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

Gle*_*ron 1 c++ polymorphism visitor

我遇到了一个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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

apply(Node&)
Drawable::print
Run Code Online (Sandbox Code Playgroud)

我期待调用Visitor :: apply(Drawable&),而是调用apply(Node&).为什么?

tem*_*def 7

看看这行代码:

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

当编译器看到这个调用apply,它需要确定您是否打算调用的版本apply是发生在一个Node&或版本apply,它接受一个Drawable&.请注意,这是决定选择哪个重载而不是选择哪个重写的决定.关于重载的决定是在编译时做出的.在这里,编译器查看表达式*node并说"好,node是一个Node *,所以*node是一个Node&",因为它无法在编译时知道指向的事物的运行时类型是什么node.因此,这将始终打电话apply(Node &),永远不会打电话apply(Drawable&).

这解释了发生了什么,但你如何解决它?获取访问者模式的传统方法是在类层次结构的基础上放置类似这样的函数:

struct Node {
    virtual void accept(Visitor* visitor);
};
Run Code Online (Sandbox Code Playgroud)

然后,您将拥有Node覆盖该函数的每个子类.然后每个覆盖看起来像这样:

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

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

要使用此accept功能,您可以编写如下内容:

virtual void accept(Node* node, Visitor* visitor)
{
    node->accept(visitor);
}
Run Code Online (Sandbox Code Playgroud)

现在,想想会发生什么.该accept成员函数被标记virtual,这样的特定版本accept调用取决于在被指向的东西类型node(即,它使用动态类型而不是静态类型).这是使用覆盖分辨率而不是重载分辨率.然后,这将调用正确的覆盖,如上所述,然后选择适当的重载.

希望这可以帮助!