C++ 在指向模板聚合类型的指针上重新解释\u强制转换的安全性

C++ 在指向模板聚合类型的指针上重新解释\u强制转换的安全性,c++,casting,C++,Casting,我想知道我下面使用的reinterpret\u cast是否是未定义的行为 给定一个模板聚合,例如 template<typename T> struct Container { Container(T* p) : ptr(p) { } ... T* ptr; }; 鉴于B是a的动态类型,下列演员是否安全 Container<B>* b = new Container<B>( new B() ); Container<A>* a =

我想知道我下面使用的
reinterpret\u cast
是否是未定义的行为

给定一个模板聚合,例如

template<typename T>
struct Container
{
  Container(T* p) : ptr(p) { }
  ...
  T* ptr;
};
鉴于
B
a
的动态类型,下列演员是否安全

Container<B>* b = new Container<B>( new B() );
Container<A>* a = reinterpret_cast<Container<A>*>(b);
Container*b=新容器(new b());
容器*a=重新解释铸件(b);
。。。我现在可以安全地使用
a->ptr
及其(可能是虚拟的)成员了吗

我使用的代码编译和执行得很好(叮当声,OSX),但我担心我放置了一个定时炸弹。我猜
容器的每个实例都有相同的布局和大小,所以应该不会有问题吧

看看cppreference.com所说的关于重新解释cast的内容,似乎有一个关于法律用途的声明涵盖了我试图做的事情

类型别名 当T1类型的对象的指针或引用被重新解释为_cast(或C样式转换)到另一类型T2的对象的指针或引用时,转换总是成功的,但只有在T1和T2都是标准布局类型且以下情况之一为真时,才能访问生成的指针或引用:

T2是一种聚合类型或联合类型,它将上述类型之一作为元素或非静态成员(递归地包括子聚合的元素和包含的联合的非静态数据成员)保存:这样可以安全地从结构的第一个成员和联合的元素强制转换为包含它的结构/联合


我很感激我在这件事上走错了方向。那不是我关心的。我只是想知道我所做的是否安全/合法。提前感谢您的帮助。

根据标准,无论类型别名是否合法,您可能会遇到其他问题

我猜
容器的每个实例都共享相同的布局和
大小应该不会有问题吧

实际上,并非每个
容器的实例都共享相同的布局!如中所述,模板成员仅在使用时才会创建,因此如果对每种类型使用不同的成员,则您的
容器
容器
可能具有不同的内存布局

似乎有一个合法用途的声明涵盖了我试图做的事情

这不是例外的意思。这个例外说明

struct S { int i; } s;
您可以使用
*重新解释(&s)
访问
s.i

对于你正在尝试做的事情,没有类似的例外。您试图做的事情在C++中根本无效。即使是以下内容也是无效的:

struct S { int i; };
struct T { int i; };
int f(S s) { return ((T &) s).i; }
编译器会根据您不这样编写代码的假设进行优化

对于使用当前编译器在运行时失败的实际示例:

#include <cstdlib>
struct S { int i; };
struct T { int i; };
void f(S *s, T *t) { int i = s->i; t->i++; if (s->i == i) std::abort(); }

从容器中派生容器,这样会自动工作。但是,这仍然不是保存,因为您可以在容器中插入一个C(它是从a派生的另一种类型),然后在容器中得到一个C,这可能不是您想要的,还有
T
的派生。问题仍然存在:如果将
容器
转换为对
容器
的可变引用,那么容器中的元素就失去了
派生
类型的保证。无论您如何实现此转换,都存在此问题,即,如果您使用暴力(重新解释_cast)或使用建议的方法从彼此派生容器,则此问题是相同的。是,如果我专门针对特定类型的
容器
,这是正确的-我明白了-但在这种情况下,任何类型的
T
都只有
ptr
,这不仅仅是因为你专门化了。成员(成员函数和成员变量)可能在不同的实例化中被忽略。在我链接到的另一个问题中,他们使用的示例是实例化一个类型无法排序的
std::list
。只要从未调用
std::list::sort
,该类就会编译。另外,假设您使用了
Container
的3个成员变量,但只使用了
Container
的两个成员变量。它们的内存布局可能不兼容。另外,如果您在每个类上使用不同的方法,那么方法地址可能不一致,并且当您在一个类上调用成员函数时,可能会跳入错误的函数或函数中间。(不太确定成员函数的情况,但我对成员变量的情况持肯定态度)。我可以理解您的推理。我想我更关心的是
ptr
的使用,而不是实际的
容器本身。您完全正确地认为使用
容器
是有风险的,但在本例中,它只充当
ptr
的包装器。谢谢你的意见。很棒的东西。
s->i==i
证明将其锁定。我的测试是用
-O0
进行的,我想这是令人担忧的部分。谢谢你,为什么这样行#包括结构S{int i[1];};结构T{int i[1];};void f(S*S,T*T){int i=S->i[0];T->i[0]++;if(S->i[0]==i)std::abort();}int main(){S S={0};f(&S,用“work”重新解释cast(&S))}@nachum,你的意思是它不会中止吗?在我在回答中指定的
-O2
级别上,它显示了从4.9开始在所有GCC版本中无条件地调用中止。啊,MacOS High Sierra上的clang(Apple LLVM版本9.0.0(clang-900.0.39.2))没有中止。但它中止了您的原始代码。
#include <cstdlib>
struct S { int i; };
struct T { int i; };
void f(S *s, T *t) { int i = s->i; t->i++; if (s->i == i) std::abort(); }
int main() { S s = { 0 }; f(&s, reinterpret_cast<T *>(&s)); }