带有解引用指针的多态性会产生意想不到的结果……为什么?

Polymorphism with a dereferenced pointer yields unexpected results...why?

我 运行 遇到了 C++ 难题,需要一些帮助! 请考虑以下代码:

#include <iostream>

struct Node
{
    virtual void print() 
    {
        std::cout << "Node::print" << std::endl;
    }
};

struct Drawable : public Node
{
    virtual void print() 
    {
        std::cout << "Drawable::print" << std::endl;
    }
};

struct Visitor
{
    virtual void apply(Node& node)
    {
        std::cout << "apply(Node&)" << std::endl;
        node.print();
    }
    virtual void apply(Drawable& drawable) 
    {
        std::cout << "apply(Drawable&)" << std::endl;
        drawable.print();
    }
};

struct Renderer
{
    virtual void accept(Node* node, Visitor* visitor)
    {
        visitor->apply(*node);
    }
};

int main(int argc, char** argv)
{
    Renderer r;
    Visitor v;
    Drawable* drawable = new Drawable();
    r.accept(drawable, &v);
    return 0;
}

输出为:

apply(Node&)
Drawable::print

我原以为会调用 Visitor::apply(Drawable&),但实际上调用了 apply(Node&)。为什么?

看看这行代码:

virtual void accept(Node* node, Visitor* visitor)
{
    visitor->apply(*node); // <--- Here
}

当编译器看到对 apply 的调用时,它需要确定您是要调用接受 Node&apply 版本还是 apply 接受 Drawable&。请注意,这是关于选择哪个 overload 而不是选择哪个 override 的决定。关于重载的决定是在编译时做出的。在这里,编译器查看表达式 *node 并说 "well, node is a Node *, so *node is a Node&" 因为它无法在编译时知道 node 指向的事物的运行时类型是什么。结果,这将始终调用 apply(Node &) 而永远不会调用 apply(Drawable&).

这解释了发生了什么,但你如何解决它?设置访问者模式的常规方法是将这样的函数放置在 class 层次结构的底部:

struct Node {
    virtual void accept(Visitor* visitor);
};

然后您可以让 Node 的每个子 class 覆盖该函数。然后每个覆盖看起来像这样:

struct Pizkwat: Node {
    virtual void accept(Visitor* visitor) override {
        visitor->apply(*this);
        // do more things, optionally
    }
};

在此覆盖的代码行中,*this 的类型在编译时将与相关对象的类型相同。这意味着 overload 选择将选择 apply 最适合该类型的版本。

要使用这个accept函数,你可以这样写:

virtual void accept(Node* node, Visitor* visitor)
{
    node->accept(visitor);
}

现在,想想会发生什么。 accept成员函数被标记为virtual,所以调用accept的具体版本取决于node指向的东西的类型(即它使用动态类型 而不是 静态类型)。这是使用 override 解决方案而不是 overload 解决方案。然后将调用正确的覆盖,正如您在上面看到的那样,然后选择正确的 overload.

希望对您有所帮助!