c++ 如何重载一个参数类型为基参数子类的函数?

yjghlzjz  于 2023-04-13  发布在  其他
关注(0)|答案(2)|浏览(144)

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

class Component abstract
{
public:
    virtual bool Method(Component& other) = 0;
};

我有一个子类,它覆盖并重载了这个函数,以接受 its class 的参数:

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)

htrmnn0y

htrmnn0y1#

编译器不知道你使用的是派生类型,也不会自动向上转换指向该类型的指针。GetComponent返回一个Component*。它可以是 * 任何 * 子类,而不仅仅是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());
deikduxw

deikduxw2#

DerivedComponent声明了Method的两个重载:一个需要Component &,另一个需要DerivedComponent &
但是重载总是静态解析的。也就是说,编译器必须在编译时决定哪个重载函数将被调用。由于该解析发生在编译时,因此它基于编译时可用的信息:指针的静态类型/引用本身,而不是它所指向/引用的对象的动态类型。
不过,有一种相当简单的方法来处理它--访问者模式(或者至少是它的一个近似变体)。
为了实现它,我们添加了另一个虚函数当我们使用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。(可能还有一些事情我暂时没有想到--但是我们正在使用继承,所以我们需要做所有常见的继承“事情”)。

相关问题