如果我有一个基本抽象类,其函数将它自己的类作为参数:
class Component abstract
{
public:
virtual bool Method(Component& other) = 0;
};
我有一个子类,它覆盖并重载了这个函数以获取它的类的参数:
class DerivedComponent : public Component
{
public:
virtual bool Method(Component& other) override;
virtual bool Method(DerivedComponent& other);
};
我将它们用作另一个类的组件,例如:
class Foo
{
private:
Component* m_component;
public:
Foo()
{
m_component = new DerivedComponent();
}
Component* GetComponent()
{
return m_component;
}
};
然后我调用组件的方法,传入
Foo
的组件:
Foo foo1 = Foo();
Foo foo2 = Foo();
foo1.GetComponent()->Method(*foo2.GetComponent());
为什么调用
DerivedComponent
的第一个未重载方法Method(Component& other)
,而不是Method(DerivedComponent& other)
?
编译器不知道你正在使用派生类型,也不会自动向上转换指向该类型的指针。
GetComponent
返回一个 Component*
。这可以是 any 子类,而不仅仅是 DerivedComponent*
.
如果你知道
Component*
实际上是一个DerivedComponent*
,你可以自己显式地转换它:
auto derivedComponent1 = static_cast<DerivedComponent&>(*foo1.GetComponent());
auto derivedComponent2 = static_cast<DerivedComponent&>(*foo2.GetComponent());
derivedComponent1.Method(derivedComponent2);
如果你不知道,并且启用了RTTI,那么你可以dynamic_cast:
auto derivedComponent1 = dynamic_cast<DerivedComponent*>(foo1.GetComponent());
auto derivedComponent2 = dynamic_cast<DerivedComponent*>(foo2.GetComponent());
if (derivedComponent1 && derivedComponent2)
derivedComponent1->Method(*derivedComponent2);
从上面的乱七八糟的东西可以看出,这不是特别理想。通常,这表明您的设计存在更根本的问题,您可能需要重新考虑。
至少,如果您需要
DerivedComponent
的特殊行为,您可以将 dynamic_cast 内容移动到您的实现中,而不是期望调用者这样做:
class DerivedComponent : public Component
{
public:
bool Method(Component& other) override
{
auto derivedComponent = dynamic_cast<DerivedComponent*>(&other);
if (derivedComponent) {
return Method(*derivedComponent);
}
// default behavior, perhaps invoking superclass method but
// in this case that is pure virtual so I guess do nothing.
return false;
}
virtual bool Method(DerivedComponent& other)
{
return true;
}
};
上面的内容现在将按照您的预期进行以下操作:
foo1.GetComponent()->Method(*foo2.GetComponent());
DerivedComponent
声明了两个 Method
的重载:一个需要一个 Component &
和一个需要一个 DerivedComponent &
.
但是重载总是静态解决的。也就是说,编译器必须在编译时决定调用哪个重载函数。由于该解决方案发生在编译时,因此它基于编译时可用的信息:指针/引用本身的静态类型,而不是它指向/引用的对象的动态类型。
虽然有一种相当干净的方法来处理它——访问者模式(或者至少是它的近似变体)。为了实现它,我们添加了另一个虚函数(我称之为
Dispatch
)。当我们使用
Method
时,它会在传递的对象上调用
Dispatch
(通过引用)。因此,我们可以获得依赖于执行调用的对象和传递的对象的行为。这是您的演示的扩展版本,显示了调用对象和传递对象的虚拟行为:
#include <iostream>
class Component
{
public:
virtual void Method(Component& other) {
std::cout << this->Dispatch() << "(" << other.Dispatch() << ")\n";
}
virtual std::string Dispatch() { return "Component"; };
};
class DerivedComponent : public Component
{
public:
std::string Dispatch() override { return "Derived"; }
};
struct Derived2 : public Component {
std::string Dispatch() override { return "Derived2"; }
};
// making this a template so it's easy to create `Foo`s that create and
// manage any of the preceding types.
template <class T>
class Foo
{
private:
Component* m_component;
public:
Foo()
{
m_component = new T;
}
Component* GetComponent()
{
return m_component;
}
};
int main() {
Foo<Component> foo;
Foo<DerivedComponent> foo1;
Foo<Derived2> foo2;
foo.GetComponent()->Method(*foo1.GetComponent());
foo1.GetComponent()->Method(*foo2.GetComponent());
foo2.GetComponent()->Method(*foo.GetComponent());
}
结果:
Component(Derived)
Derived(Derived2)
Derived2(Component)
当然,在实际使用中我们还需要处理其他一些事情——Foo
可能需要一个 dtor 来销毁它创建的
Component
(或派生)对象。它可能还需要在复制、移动和赋值方面做一些事情(但它究竟应该做什么是一个单独的问题)。由于我们将其用作基类,并且可能会通过指向基类的指针/引用销毁派生对象,因此
Component
需要一个虚拟的
dtor
。 (现在可能还有一些我没有想到的事情——但我们正在使用继承,所以我们需要做所有通常的继承“事情”)。