带有解引用指针的多态性会产生意想不到的结果……为什么?
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.
希望对您有所帮助!
我 运行 遇到了 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.
希望对您有所帮助!