为什么C样式转换的行为与dynamic_cast不同
Why does C-style cast behave differently than dynamic_cast?
我有以下类层次结构:
class IControl
{
virtual void SomeMethod() = 0; // Just to make IControl polymorphic.
};
class ControlBase
{
public:
virtual int GetType() = 0;
};
class ControlImpl : public ControlBase, public IControl
{
public:
virtual void SomeMethod() { }
virtual int GetType()
{
return 1;
}
};
我有一个IControl抽象类和一个
ControlBase类。ControlBase类不是从IControl继承的,但我知道每个IControl实现都将从ControlBase派生。
我有以下测试代码,其中我用dynamic_cast和C-style转换了一个IControl-对ControlBase的引用(因为我知道它派生自它):
int main()
{
ControlImpl stb;
IControl& control = stb;
ControlBase& testCB1 = dynamic_cast<ControlBase&>(control);
ControlBase& testCB2 = (ControlBase&)control;
ControlBase* testCB3 = (ControlBase*)&control;
std::cout << &testCB1 << std::endl;
std::cout << &testCB2 << std::endl;
std::cout << testCB3 << std::endl;
std::cout << std::endl;
std::cout << testCB1.GetType() << std::endl; // This properly prints "1".
std::cout << testCB2.GetType() << std::endl; // This prints some random number.
std::cout << testCB3->GetType() << std::endl; // This prints some random number.
}
只有dynamic_cast工作正常,其他两个强制转换返回的内存地址略有不同,GetType()函数返回的值不正确。
这到底是什么原因?C样式的强制转换是否最终使用interpret_cast?它与多态对象在内存中的排列方式有关吗?
我认为您的示例中的类名有点令人困惑。让我们称它们为Interface
、Base
和Impl
。注意,Interface
和Base
是不相关的。
C++标准定义了C样式转换,在[expr.cast]中称为"显式类型转换(转换表示法)"。您可以(也许应该)阅读整段内容,以确切了解C样式转换是如何定义的。对于OP中的示例,以下内容就足够了:
C样式可以执行[expr.cast]/4:之一的转换
const_cast
static_cast
static_cast
后为const_cast
reinterpret_cast
reinterpret_cast
后为const_cast
这个列表的顺序很重要,因为:
如果转换可以用以上列出的一种以上方式进行解释,则使用列表中第一个出现的解释,即使该解释产生的转换形式不正确。
让我们检查一下您的示例
Impl impl;
Interface* pIntfc = &impl;
Base* pBase = (Base*)pIntfc;
不能使用const_cast
,列表中的下一个元素是static_cast
。但类Interface
和Base
是不相关的,因此没有static_cast
可以从Interface*
转换为Base*
。因此,使用reinterpret_cast
。
附加说明:您的问题的实际答案是:由于上面的列表中没有dynamic_cast
,因此C样式转换的行为永远不会像dynamic_cast
那样。
实际地址的变化不是C++语言定义的一部分,但我们可以举一个如何实现的例子:
具有至少一个虚拟函数(继承的或自己的)的类的每个对象都包含(在本例中,读取:可能包含)指向vtable的指针。如果它从多个类继承虚拟函数,那么它包含多个指向vtables的指针。由于空基类优化(没有数据成员),Impl
的实例可能如下所示:
+=Impl=======================================+|||+-底座-------++-接口-------++|||vtable_Base*||vtable_Interface*|||+----------------+----------------+|||+============================================+
现在,示例:
Impl impl;
Impl* pImpl = &impl;
Interface* pIntfc = pImpl;
Base* pBase = pImpl;
+=Impl=======================================+|||+-底座-------++-接口-------++|||vtable_Base*||vtable_Interface*|||+----------------+----------------+||^^|+==|==================|======================+^|||+--pBase+--pIntfc|+--皮条客
如果您改为执行reinterpret_cast
,则结果是由实现定义的,但它可能会导致以下结果:
Impl impl;
Impl* pImpl = &impl;
Interface* pIntfc = pImpl;
Base* pBase = reinterpret_cast<Base*>(pIntfc);
+=Impl=======================================+|||+-底座-------++-接口-------++|||vtable_Base*||vtable_Interface*|||+----------------+----------------+||^|+=====================|======================+^||+--pIntfc||+--皮条+--pBase
即地址不变,pBase
指向Impl
对象的Interface
子对象。
请注意,取消引用指针pBase
已经将我们带到UB土地,标准没有指定应该发生什么。在此示例性实现中,如果调用pBase->GetType()
,则使用包含SomeMethod
条目的vtable_Interface*
,并调用该函数。这个函数不返回任何东西,所以在这个例子中,鼻妖被召唤并接管世界。或者从堆栈中获取某个值作为返回值。
这到底是什么原因?
确切的原因是标准保证dynamic_cast
在这种情况下工作,而其他类型调用未定义的行为。
C样式的强制转换最终会使用repret_cast吗?
是的,在这种情况下确实如此。(附带说明:永远不要使用C样式转换)。
它与多态对象在内存中的排列方式有关吗?
我认为这与使用多重继承的多态对象在内存中的布局方式有关。在具有单一继承的语言中,dynamic_cast
将不是必需的,因为基子对象地址将与派生对象地址一致。在多重继承的情况下,情况并非如此,因为有多个基子对象,并且不同的基子对象必须具有不同的地址。
有时编译器可以计算每个子对象地址和派生对象地址之间的偏移量。如果偏移量为非零,则强制转换操作将变为指针加法或减法,而不是no-op。(在虚拟继承upcast的情况下,它稍微复杂一些,但编译器仍然可以做到这一点)。
编译器至少有两种情况不能做到这一点:
- 交叉转换(也就是说,在两个类之间,这两个类都不是另一个的基类)
- 从虚拟基地进行的向下广播
在这些情况下,dynamic_cast
是唯一的投射方式。
- 如何理解C++标准N3337中的expr.const.cast子句8
- C++Cast运算符过载
- 在成员dynamic_bitset上使用 boost::from_block_range 时出错,但在本地dynamic
- C++类中的二维"dynamic"数组?
- 错误:"cast"未命名类型void setCastDescription(std::string
- 通过使用 const-cast 的非常量引用来延长临时的寿命
- "(void) cast"与功能有什么区别 "__attributes__"来沉默未使用的参数警告?
- protobuf in C++ with dynamic binding for google::protobuf::M
- 警告的原因是什么:"when type is in parentheses, array cannot have dynamic size"?
- C++:"Expected '(' for function-style cast or type construction"错误
- 为什么选择 g++ 给予者:"error: cast to pointer from integer of different size [-Werror=int-to-pointer-cast]"
- CUDA 错误:"dynamic initialization is not supported for __device__, __constant__ and __shared__ variabl
- Gtk+ g_signal_connect() 和 C++ lambda 会导致"invalid cast"错误
- 如何修复'The procedure entry point SDL_RWclose could not be located in the dynamic link library'
- Shared_ptr cast vs static_cast speed
- 在 iOS 上使用 Aruco 构建 OpenCV 时"Functional-style cast from id to double is not allowed"
- "The ordinal 344 could not be located in the dynamic link library"
- 覆盖 CAST 运算符(我认为它被称为向下转换)
- Dynamic Cast C++ Fail
- dynamic-cast-c++dynamic_cast错误处理