如何使用类型为基参数子类的参数重载函数?

问题描述 投票:0回答:2

如果我有一个基本抽象类,其函数将它自己的类作为参数:

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)

c++ inheritance overloading abstract-class derived-class
2个回答
3
投票

编译器不知道你正在使用派生类型,也不会自动向上转换指向该类型的指针。

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

2
投票

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
。 (现在可能还有一些我没有想到的事情——但我们正在使用继承,所以我们需要做所有通常的继承“事情”)。

© www.soinside.com 2019 - 2024. All rights reserved.