C++模板类型间使用reinterpret_cast的行为是否为明确定义?
C++模板仿函数reinterpret_cast行为合法性问题
测试代码
#include <iostream> using namespace std; //--------------------Foo------------------ template<int Index> class Foo { public: Foo(string first, string second, string third) { foo_[0] = first; foo_[1] = second; foo_[2] = third; } string operator()() const { return foo_[Index]; } private: string foo_[3]; }; //---------------------Bar------------------ class BarBase { public: virtual string operator()() const { return "BarBase"; }; }; template<int Index> class Bar : public BarBase { public: Bar(string first, string second, string third) { bar_[0] = first; bar_[1] = second; bar_[2] = third; } string operator()() const { return bar_[Index]; } private: string bar_[3]; }; //---------------------Wrapper------------------ class WrapperBase { public: virtual string operator()() const { return "WrapperBase"; }; }; template<typename T> class Wrapper : public WrapperBase { public: Wrapper(T* functor) : functor_(functor) {} string operator()() const { return (*functor_)(); } private: T* functor_; }; int main() { Foo<0> foo0("A", "B", "C"); Foo<1>& foo1 = *reinterpret_cast<Foo<1>*>(&foo0); Foo<2>& foo2 = *reinterpret_cast<Foo<2>*>(&foo0); cout<< "foo: " << foo1() << foo2() <<"\n"; Bar<0> bar0("A", "B", "C"); Bar<1>& bar1 = *reinterpret_cast<Bar<1>*>(&bar0); Bar<2>& bar2 = *reinterpret_cast<Bar<2>*>(&bar0); cout<< "bar: " << bar1() << bar2() <<"\n"; WrapperBase* wrappedfoo0 = new Wrapper<Foo<0>>(&foo0); WrapperBase* wrappedfoo1 = new Wrapper<Foo<1>>(&foo1); WrapperBase* wrappedfoo2 = new Wrapper<Foo<2>>(&foo2); cout<< "wrapped foo: " << (*wrappedfoo1)() << (*wrappedfoo2)() <<"\n"; return 0; }
运行输出
foo: BC bar: AA wrapped foo: BC
原问题描述
Foo和Bar逻辑完全一致,唯一区别是Bar继承自基类且实现的operator()为虚函数,因此Bar类实例带有虚函数指针,而Foo没有。
我目前理解bar输出AA而foo输出BC的原因,若有误恳请指正:两个类各有3个模板实例,对应3个operator()实现,各自的模板参数Index是硬编码的。但由于Bar带有虚函数指针,将Bar<0>通过reinterpret_cast转换为Bar<1>后,虚函数指针仍指向Bar<0>的函数实现。
我想确认这段代码的行为是否为明确定义,尤其是"Foo"和"Wrapped Foo"场景:只要仿函数不使用继承,就可以将其reinterpret_cast为其他Foo类型,调用operator()时会使用当前变量(或是Wrapper实例化时使用的)模板参数对应的Index?
补充提问
如果移除Foo的构造函数,将foo_成员改为public并从外部初始化,情况如何?
此时Foo属于POD类型,C++标准(9.2.18)中关于reinterpret_cast和POD的说明如下:
指向POD结构体对象的指针,经reinterpret_cast适当转换后,会指向其首个成员(若该成员为位域,则指向其所在的存储单元),反之亦然。[注:为满足对齐要求,POD结构体内部可能存在未命名填充,但起始位置不会有填充。
因此若移除Foo的构造函数,Foo及Wrapped Foo场景下的行为是否为明确定义?
解答
- 你对Bar输出AA的理解是正确的:虚函数调用依赖对象实际内存里存储的虚表指针,reinterpret_cast只是对指针的类型做了翻译,不会修改原对象的内存内容,因此不管你把Bar<0>实例转成哪一个Bar子类的引用,调用虚函数operator()始终会触发Bar<0>的实现,返回bar_[0]也就是A,最终输出AA。
- Foo和Wrapped Foo场景的行为是未定义行为,和Foo是否带继承无关:
Foo<0>和Foo<1>、Foo<2>是完全独立的不同类型,哪怕它们的内存布局看起来完全一致,C++标准也没有规定同模板不同参数的实例布局必须兼容,你通过不匹配的类型左值访问原对象,已经违反了严格别名规则,你看到输出BC只是当前编译器特定编译配置下的巧合结果,换编译环境、开优化等级都可能出现完全不符合预期的输出。 - 补充提问场景依然是未定义行为:你提到的POD相关规则只适用于把结构体指针转换为它第一个成员的指针的场景,完全不适用于两个不同类型的结构体互相转换的情况,哪怕二者都是POD也不行,改完POD也不能让这段代码的行为变得合法。
内容的提问来源于stack exchange,提问作者matthias_buehlmann
相关产品推荐
相关产品推荐

