实现虚拟方法的组合行为

Implementing compositional behaviour for virtual methods

本文关键字:组合 虚拟 方法 实现      更新时间:2023-10-16

假设我有几个类的层次结构:

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();
...
}

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

另类:

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的回答中描述了一个想法,我觉得它不合适,因为我认为对于长继承链来说,这是丑陋的——必须为每个层次结构级别使用不同的方法名称。

您可以将类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;
};

我不确定我是否正确理解,但这似乎可以通过"使公共接口非虚拟,虚拟化私有功能"的建议很好地解决。

我认为这是在开闭原则中体现的。该技术如下:

#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 stuffn";
}
};
int main() {
D d;
d.f();
return 0;
}

您基本上剥夺了后代类覆盖公共接口的权限,仅自定义公开的部分。基类B严格强制要求在派生中的实际实现之前调用所需的方法。作为奖励,您不必记住调用基类。

当然,您也可以f虚拟的,D决定。