为虚拟方法实现组合行为

时间:2019-07-09 14:40:08

标签: c++ composition virtual-functions virtual-inheritance

假设我有几个类的继承人:

class A {
public:
    virtual void DoStuff() = 0;
};

class B : public A {
public:
    // Does some work
    void DoStuff() override;
};

class C : public B {
public:
   // Calls B::DoStuff and does other work
   void DoStuff() override;
};

它可以天真地实现:

void Derived::DoStuff() {
    Base::DoStuff();
    ...
}

我相信此实现有一个严重的问题:人们总是必须记住在覆盖时调用基本实现。

替代:

class A {
public:
    void DoStuff() {
        for (auto& func: callbacks_) {
            func(this);
        }
    }

    virtual ~A() = default;
protected:
    template <class T>
    void AddDoStuff(T&& func) {
        callbacks_.emplace_back(std::forward<T>(func));
    }

private:
    template <class... Args>
    using CallbackHolder = std::vector<std::function<void(Args...)>>;

    CallbackHolder<A*> callbacks_;
};

用法:

class Derived : public Base {
public:
    Derived() {
        AddDoStuff([](A* this_ptr){
            static_cast<Derived*>(this_ptr)->DoStuffImpl();
        });
    }
private:
    void DoStuffImpl();
};

但是,我认为与第一个实现相比,实际调用DoStuff()时有很多开销。在我看到的用例中,对象的长构建可能不是问题(如果他愿意,也可以尝试实现“短向量优化”之类的东西)。

此外,我相信每种DoStuff方法的3个定义都有些重复。

我知道可以通过使用类似于CRTP的继承模式来非常有效地解决此问题,并且可以将基于模板的解决方案隐藏在接口类(示例中为A)之后,但是我一直想知道-应该有没有更简单的解决方案?

我对长继承链(或类似的东西)的call DERIVED implementation FROM BASE, if and only if derived class exists and it has an overriding method的良好实现感兴趣。

谢谢!

编辑: 我知道@ Jarod42的答案中描述的一种想法,但我认为它不合适,因为我认为对于长的继承链来说这很丑陋-每个层次结构都必须使用不同的方法名称。

2 个答案:

答案 0 :(得分:2)

您可以将您的课程B更改为:

class A {
public:
    virtual ~A() = default;
    virtual void DoStuff() = 0;
};

class B : public A {
public:
    void DoStuff() final { /*..*/ DoExtraStuff();  }

    virtual void DoExtraStuff() {}
};

class C : public B {
public:
   void DoExtraStuff() override;
};

答案 1 :(得分:2)

我不确定我是否理解正确,但是"Make public interface non-virtual, virtualize private functions instead" 的建议似乎可以很好地解决这个问题。

我认为它是在开放式封闭原则下制定的。该技术如下:

#include <iostream>
class B {
    public:
    void f() {
        before_f();
        f_();
    };

    private:
    void before_f() {
        std::cout << "will always be before f";
    }

    virtual void f_() = 0;
};

class D : public B{
    private:
    void f_() override {
        std::cout << "derived stuff\n";
    }
};

int main() {
    D d;
    d.f();
    return 0;
}

您实质上剥夺了后代类的重写公共接口,仅自定义了公开的部分。基类B严格要求在派生类可能要执行的实际实现之前调用必需的方法。作为奖励,您不必记得打电话给基类。

当然,您也可以将f虚拟化,并让D做出决定。