我想出了一些令人困惑的情况。下面是它们。
#include <iostream>
using namespace std;
class Base {
public:
Base(int num) : data(num) {
cout << "BASE : " << num << endl;
}
virtual ~Base() = default;
virtual void func1() {
cout << "Base func1 called : " << data << endl;
}
virtual void func3() {
cout << "Base func3 called : " << data << endl;
}
private:
int data;
};
class Interface {
public:
Interface() {
cout << "INTERFACE : " << endl;
}
virtual ~Interface() = default;
virtual void func2() {
cout << "Interface func2 called" << endl;
}
};
class Derived : public Interface, public Base {
public:
Derived() : Base(0) {
cout << "DERIVED : hh" << endl;
}
virtual ~Derived() = default;
virtual void func1() override {
cout << "Derived fuc1 called" << endl;
}
virtual void func3() override {
cout << "Derived fuc3 called" << endl;
}
virtual void func2() override {
cout << "Derived fuc2 called" << endl;
}
};
int main() {
//Interface* a = new Derived(); // derived func2 called
//Base* a = new Derived(); // derived func1 called
//Derived* a = new Derived(); // derived func2 called
void* a = new Derived(); // derived func1 called
auto b = (Interface*)a;
b->func2();
...
}
当执行b->func2()
时,结果因变量 a 的显式类型而异。
结果在评论中。
为什么它们在执行b->func2()
时不同?
void* a = new Derived(); // derived func1 called
auto b = (Interface*)a;
这是未定义的行为。将指向派生类的指针转换为指向其基类的指针的常用语义仅适用于将派生类的指针转换为指向其基类的指针的情况。但这不是这里发生的事情。
中间转换为其他指针,如void *
会使所有保修失效(双关语不是故意的)。
void* a = new Derived();
auto b = (Interface*)a;
这是未定义的行为。 您不会将void*
转换回存储在其中的相同类型 (Derived*
)。
如果要将void*
转换为Interface*
,则需要首先在void*
中存储一个Interface*
,例如:
void* a = static_cast<Interface*>(new Derived());
auto b = static_cast<Interface*>(a);
当通过对象指针调用virtual
方法时,编译器取消引用对象指针以访问指向属于对象指针所指向类型的 vtable 的隐藏指针,然后索引到该 vtable 中以了解要调用哪个类方法。
Derived
对象在内存中看起来像这样(详细信息可能因编译器而异,但这是它的要点1):
+-------+
+-> | ~Base |--> &Derived::~Derived()
| | func1 |--> &Derived::func1()
+---------------+ | | func3 |--> &Derived::func3()
| Base vmt |--+ +-------+
|---------------| +------------+
| Interface vmt |------> | ~Interface |--> &Derived::~Derived()
|---------------| | func2 |--> &Derived::func2()
| Derived vmt |--+ +------------+
+---------------+ | +------------+
+-> | ~Base |--> &Derived::~Derived()
| func1 |--> &Derived::func1()
| func3 |--> &Derived::func3()
| ~Interface |--> &Derived::~Derived()
| func2 |--> &Derived::func2()
| ~Derived |--> &Derived::~Derived()
+------------+
一个Derived
对象由Base
、Interface
和Derived
拼凑在一起的所有东西组成。 每个类仍然有自己的指针,指向属于该类的虚拟方法表。 但是由于Derived
实现了所有virtual
方法,因此Derived
对象有多个vtable指针,并且它们都引用Derived
的方法。
1:为了提高效率,Derived
可能只有 1 个 vtable,并且其所有 3 个 vmt 指针都指向该单个表的不同区域。 但这是一个实现细节,对于本答案而言并不重要。
当a
被声明为Interface*
时,它指向Derived
对象的Interface
部分:
+---------------+
| Base vmt |
|---------------|
a -> | Interface vmt |
|---------------|
| Derived vmt |
+---------------+
a
Interface*
的类型转换实际上是一种无操作,导致b
成为指向Derived
对象的Interface
部分的Interface*
指针:
+---------------+
| Base vmt |
|---------------|
b -> | Interface vmt |
|---------------|
| Derived vmt |
+---------------+
因此,调用b->func2()
使用Interface'
的 vtable,如预期的那样跳转到第 2 个条目,该条目Derived::func2()
。
当a
被声明为Base*
时,它指向Derived
对象的Base
部分:
+---------------+
a -> | Base vmt |
|---------------|
| Interface vmt |
|---------------|
| Derived vmt |
+---------------+
但是,Interface*
a
的类型强制转换是未定义的行为,因为Base
和Interface
彼此不相关,导致b
成为指向Derived
对象Base
部分的Interface*
指针:
+---------------+
b -> | Base vmt |
|---------------|
| Interface vmt |
|---------------|
| Derived vmt |
+---------------+
因此,调用b->func2()
错误地使用了Base
的 vtable 而不是Interface
的 vtable,意外地跳到了第 2 个条目,这是Derived::func1()
。
如果您在这里使用static_cast
而不是 C 样式的强制转换,编译就会失败(这就是为什么您应该避免在 C++ 中使用 C 样式转换!
当a
被声明为Derived*
时,它指向Derived
对象的Derived
部分:
+---------------+
| Base vmt |
|---------------|
| Interface vmt |
|---------------|
a -> | Derived vmt |
+---------------+
类型转换a
Interface*
是明确定义的,因为Interface
是Derived
的基,导致b
是指向Derived
对象的Interface
部分的Interface*
指针:
+---------------+
| Base vmt |
|---------------|
b -> | Interface vmt |
|---------------|
| Derived vmt |
+---------------+
所以调用b->func2()
会使用Interface
的 vtable,如预期的那样跳转到第 2 个条目,该条目Derived::func2()
。
当a
被声明为void*
时,它指向Derived
对象的Derived
部分:
+---------------+
| Base vmt |
|---------------|
| Interface vmt |
|---------------|
a -> | Derived vmt |
+---------------+
但是,类型转换a
Interface*
是未定义的行为,因为a
不指向Derived
对象的Interface
部分,从而导致指向Derived
对象的Derived
部分的Interface*
指针:
+---------------+
| Base vmt |
|---------------|
| Interface vmt |
|---------------|
b -> | Derived vmt |
+---------------+
因此,调用b->func2()
错误地使用了Derived
的 vtable 而不是Interface
的 vtable,意外地跳到了第 2 个条目,这是Derived::func1()
。
行为是未定义的,因为您没有将void *
转换回实际类型。
你要么需要做
void *a = (Interface*)(new Derived());
auto b = (Interface *)a;
或在两者之间添加一个额外的步骤
void* a = new Derived();
auto temp = (Derived *)a; // explicitly convert the void pointer to the actual type
auto b = (Interface*)temp;
相当于
void* a = new Derived();
auto b = (Interface*)((Derived *)a);
无论哪种方式,如果没有在转换为Interface *
之前将void *
转换为Derived *
,则行为是不确定的。
考虑使用其中一种_cast
(例如static_cast
) 用于转换,而不是 C 样式的强制转换。