从多态容器中提取已知接口时的奇怪行为
Weird behaviour when extracting a known interface from a polymorphic container
谁能帮我理解这种行为?
简而言之:
- 我已经将多态对象存储在一个公共容器中。
- 其中一些实现了特定的接口。我可以分辨出哪些。
- 但是我不能使用这个界面
这是我归结为:
#include <iostream>
#include <vector>
// A base class
struct Base {
// A polymorphic method
virtual void describe() const {
std::cout << "Base" << std::endl;
};
virtual ~Base(){
std::cout << " Base destroyed" << std::endl;
};
};
// A specific interface
struct Interface {
virtual ~Interface(){
std::cout << " Interface Destroyed" << std::endl;
};
virtual void specific() = 0;
};
// A derived class..
struct Derived : public Base, public Interface {
virtual void describe() const {
std::cout << "Derived" << std::endl;
};
virtual void specific() {
std::cout << "Derived uses Interface" << std::endl;
};
virtual ~Derived() {
std::cout << " Derived destroyed" << std::endl;
};
};
int main() {
// Test polymorphism:
Base* b( new Base() );
Derived* d( new Derived() );
b->describe(); // "Base"
d->describe(); // "Derived"
// Ok.
// Test interface:
d->specific(); // "Derived uses Interface"
Interface* i(d);
i->specific(); // "Derived uses Interface"
// Ok.
// Here is the situation: I have a container filled with polymorphic `Base`s
std::vector<Base*> v {b, d};
// I know that this one implements the `Interface`
Interface* j((Interface*) v[1]);
j->specific(); // " Derived destroyed"
// " Interface destroyed"
// " Base destroyed"
// Why?! What did that object do to deserve this?
return EXIT_SUCCESS; // almost -_-
}
谁能告诉我我在那里缺少什么?
有趣的事实:如果我交换 Base::~Base
和 Base::describe
的定义,那么对象会描述自身而不是被销毁。为什么方法声明中的顺序很重要?
这是避免 C 风格转换的一个很好的理由。当你这样做时:
Interface* j((Interface*) v[1]);
那是一个reinterpret_cast
。 C 风格的转换将尝试按顺序执行:const_cast
、static_cast
、static_cast
然后 const_cast
、reinterpret_cast
、reinterpret_cast
然后 const_cast
。在这种情况下,所有这些演员表都是错误的! reinterpret_cast
尤其是未定义的行为,老实说,为什么您会看到您看到的特定行为并不重要†。未定义的行为是未定义的。
你想做的是:
Interface* j = dynamic_cast<Interface*>(v[1]);
这是通过运行时动态层次结构的正确转换,会给你 正确的 Interface*
对应 v[1]
(或 nullptr
,如果 v[1]
没有此运行时类型)。一旦我们解决了这个问题,然后 j->specific()
打印 Derived uses Interface
,正如您所期望的那样。
†问题可能与 vtable 对齐有关。当您进行重新解释转换时,由于 Base
没有 specific
,因此该特定函数的偏移量可能与 ~Base()
对齐,因此效果是您直接调用析构函数——这就是为什么你看到你所看到的。
谁能帮我理解这种行为? 简而言之:
- 我已经将多态对象存储在一个公共容器中。
- 其中一些实现了特定的接口。我可以分辨出哪些。
- 但是我不能使用这个界面
这是我归结为:
#include <iostream>
#include <vector>
// A base class
struct Base {
// A polymorphic method
virtual void describe() const {
std::cout << "Base" << std::endl;
};
virtual ~Base(){
std::cout << " Base destroyed" << std::endl;
};
};
// A specific interface
struct Interface {
virtual ~Interface(){
std::cout << " Interface Destroyed" << std::endl;
};
virtual void specific() = 0;
};
// A derived class..
struct Derived : public Base, public Interface {
virtual void describe() const {
std::cout << "Derived" << std::endl;
};
virtual void specific() {
std::cout << "Derived uses Interface" << std::endl;
};
virtual ~Derived() {
std::cout << " Derived destroyed" << std::endl;
};
};
int main() {
// Test polymorphism:
Base* b( new Base() );
Derived* d( new Derived() );
b->describe(); // "Base"
d->describe(); // "Derived"
// Ok.
// Test interface:
d->specific(); // "Derived uses Interface"
Interface* i(d);
i->specific(); // "Derived uses Interface"
// Ok.
// Here is the situation: I have a container filled with polymorphic `Base`s
std::vector<Base*> v {b, d};
// I know that this one implements the `Interface`
Interface* j((Interface*) v[1]);
j->specific(); // " Derived destroyed"
// " Interface destroyed"
// " Base destroyed"
// Why?! What did that object do to deserve this?
return EXIT_SUCCESS; // almost -_-
}
谁能告诉我我在那里缺少什么?
有趣的事实:如果我交换 Base::~Base
和 Base::describe
的定义,那么对象会描述自身而不是被销毁。为什么方法声明中的顺序很重要?
这是避免 C 风格转换的一个很好的理由。当你这样做时:
Interface* j((Interface*) v[1]);
那是一个reinterpret_cast
。 C 风格的转换将尝试按顺序执行:const_cast
、static_cast
、static_cast
然后 const_cast
、reinterpret_cast
、reinterpret_cast
然后 const_cast
。在这种情况下,所有这些演员表都是错误的! reinterpret_cast
尤其是未定义的行为,老实说,为什么您会看到您看到的特定行为并不重要†。未定义的行为是未定义的。
你想做的是:
Interface* j = dynamic_cast<Interface*>(v[1]);
这是通过运行时动态层次结构的正确转换,会给你 正确的 Interface*
对应 v[1]
(或 nullptr
,如果 v[1]
没有此运行时类型)。一旦我们解决了这个问题,然后 j->specific()
打印 Derived uses Interface
,正如您所期望的那样。
†问题可能与 vtable 对齐有关。当您进行重新解释转换时,由于
Base
没有 specific
,因此该特定函数的偏移量可能与 ~Base()
对齐,因此效果是您直接调用析构函数——这就是为什么你看到你所看到的。