C++多接口继承和static_cast
C++ multiple interfaces inheritance and static_cast
在下面的代码中:
#include <iostream>
#include <thread>
#include <mutex>
#include <functional>
struct IView {
virtual void setOnClick() = 0;
};
struct ITextView : IView {
virtual void setText() = 0;
};
struct IButton : ITextView {
virtual void setRadius() = 0;
};
struct View : IView {
int i = 1;
virtual void setOnClick() override {
std::cout << "setting OnClick! i: " << i << std::endl;
};
};
/// Works as is
/// But if make "TextView : View, ITextView" - have segfault on the run
struct TextView : ITextView, View {
int j = 2;
virtual void setText() override {
std::cout << "setting text! i: " << i << " j: " << j << std::endl;
};
// forward IView
virtual void setOnClick() override {
View::setOnClick();
}
};
int main() {
TextView tv;
void* ptr = &tv; // I need to pass raw pointer, and then restore "interface" from it
ITextView* itv = static_cast<ITextView*>(ptr); // I don't need safety checks here
itv->setOnClick();
itv->setText();
return 0;
}
如果我更改 TextView
的继承顺序,我将在 itv->setText();
调用时出现段错误。
为什么重要?我这里可以用static_cast
,还是我这里有UB?据我了解,dynamic_cast
只需要虚拟继承,而据我所知,情况并非如此。
指向不同class"slices"的指针是不同的。
所以当你通过void *传输时,你需要
void * ptr = static_cast<ITextView *>(&tv);
...
ITextView* itv = static_cast<ITextView*>(ptr)
请注意,您的基础 class 层次结构中有两个 "instances" IView:
TextView <- ITextView <- ... <- IView
\- View <- IView
有虚拟继承把它变成"diamond":wikipedia
就目前而言,您从 TextView*
隐式转换为 void*
,然后从 void*
显式转换为 ITextView*
。这些转换在转换 from/to void*
时不执行任何指针调整,因此您最终得到一个 ITextView*
类型的指针,它实际上指向 TextView
(而不是它的 ITextView
子对象!):未定义的行为随之而来。
解决方案是 始终 在 void*
:
的两个 "sides" 上使用完全相同的类型
TextView tv;
void* ptr = static_cast<ITextView*>(&tv); // Adjust, then convert to void*
ITextView* itv = static_cast<ITextView*>(ptr);
重点是您正在对通过 void*
向编译器隐藏实际类型的不同类型执行 static_cast
。
我的意思是:
TextView tv;
ITextView* itv = static_cast<ITextView*>(&tv);
这是安全的,您正在显式转换为父类型。
TextView tv;
void* ptr = &tv;
ITextView* itv = static_cast<ITextView*>(ptr);
这里编译器不知道 ptr
的实际类型并将其视为 ITextView*
,因此它无法将指针调整到对象的正确子部分(它是值得注意的是,这似乎可行,因为 ITextView
没有任何成员变量)。这与继承是如何在引擎盖下实现的有关,想想:
---------------------
| VTable A | Type A |
---------------------
---------------------
| VTable B | Type B |
---------------------
-----------------------------------------
| VTable AB | Type A | Type B | Type AB |
-----------------------------------------
假设这是
的内存布局
class A { /* members */ };
class B { /* members */ };
class AB : public A, public B { /* members */ };
现在很明显,如果你有一个 AB
的地址并且你将它转换为一个 B*
而没有让编译器知道它是一个 AB
那么编译器无法指向正确的 Type B 部分,该部分位于 Type A 切片之后。只有首先知道它是 AB
才能知道这一点。
在下面的代码中:
#include <iostream>
#include <thread>
#include <mutex>
#include <functional>
struct IView {
virtual void setOnClick() = 0;
};
struct ITextView : IView {
virtual void setText() = 0;
};
struct IButton : ITextView {
virtual void setRadius() = 0;
};
struct View : IView {
int i = 1;
virtual void setOnClick() override {
std::cout << "setting OnClick! i: " << i << std::endl;
};
};
/// Works as is
/// But if make "TextView : View, ITextView" - have segfault on the run
struct TextView : ITextView, View {
int j = 2;
virtual void setText() override {
std::cout << "setting text! i: " << i << " j: " << j << std::endl;
};
// forward IView
virtual void setOnClick() override {
View::setOnClick();
}
};
int main() {
TextView tv;
void* ptr = &tv; // I need to pass raw pointer, and then restore "interface" from it
ITextView* itv = static_cast<ITextView*>(ptr); // I don't need safety checks here
itv->setOnClick();
itv->setText();
return 0;
}
如果我更改 TextView
的继承顺序,我将在 itv->setText();
调用时出现段错误。
为什么重要?我这里可以用static_cast
,还是我这里有UB?据我了解,dynamic_cast
只需要虚拟继承,而据我所知,情况并非如此。
指向不同class"slices"的指针是不同的。
所以当你通过void *传输时,你需要
void * ptr = static_cast<ITextView *>(&tv);
...
ITextView* itv = static_cast<ITextView*>(ptr)
请注意,您的基础 class 层次结构中有两个 "instances" IView:
TextView <- ITextView <- ... <- IView
\- View <- IView
有虚拟继承把它变成"diamond":wikipedia
就目前而言,您从 TextView*
隐式转换为 void*
,然后从 void*
显式转换为 ITextView*
。这些转换在转换 from/to void*
时不执行任何指针调整,因此您最终得到一个 ITextView*
类型的指针,它实际上指向 TextView
(而不是它的 ITextView
子对象!):未定义的行为随之而来。
解决方案是 始终 在 void*
:
TextView tv;
void* ptr = static_cast<ITextView*>(&tv); // Adjust, then convert to void*
ITextView* itv = static_cast<ITextView*>(ptr);
重点是您正在对通过 void*
向编译器隐藏实际类型的不同类型执行 static_cast
。
我的意思是:
TextView tv;
ITextView* itv = static_cast<ITextView*>(&tv);
这是安全的,您正在显式转换为父类型。
TextView tv;
void* ptr = &tv;
ITextView* itv = static_cast<ITextView*>(ptr);
这里编译器不知道 ptr
的实际类型并将其视为 ITextView*
,因此它无法将指针调整到对象的正确子部分(它是值得注意的是,这似乎可行,因为 ITextView
没有任何成员变量)。这与继承是如何在引擎盖下实现的有关,想想:
---------------------
| VTable A | Type A |
---------------------
---------------------
| VTable B | Type B |
---------------------
-----------------------------------------
| VTable AB | Type A | Type B | Type AB |
-----------------------------------------
假设这是
的内存布局class A { /* members */ };
class B { /* members */ };
class AB : public A, public B { /* members */ };
现在很明显,如果你有一个 AB
的地址并且你将它转换为一个 B*
而没有让编译器知道它是一个 AB
那么编译器无法指向正确的 Type B 部分,该部分位于 Type A 切片之后。只有首先知道它是 AB
才能知道这一点。