我知道C ++不支持容器元素的协方差,就像在Java或C#中一样。所以下面的代码可能是未定义的行为:
#include <vector>
struct A {};
struct B : A {};
std::vector<B*> test;
std::vector<A*>* foo = reinterpret_cast<std::vector<A*>*>(&test);
毫不奇怪,我在向another question提出解决方案时收到了downvotes。
但是C ++标准的哪一部分确切地告诉我这将导致未定义的行为?保证std::vector<A*>
和std::vector<B*>
将指针存储在连续的内存块中。它也保证sizeof(A*) == sizeof(B*)
。最后,A* a = new B
完全合法。
那么标准中的不良精神是什么(风格除外)?
答案 0 :(得分:16)
此处违反的规则记录在C ++ 03 3.10 / 15 [basic.lval]中,它指定非正式地称为“严格别名规则”
如果程序试图通过以下类型之一以外的左值访问对象的存储值,则行为未定义:
对象的动态类型
对象动态类型的cv限定版本
与对象的动态类型对应的有符号或无符号类型的类型,
对应于对象动态类型的cv限定版本的有符号或无符号类型
聚合或联合类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合的成员),
一种类型,它是对象动态类型的(可能是cv限定的)基类类型,
char或unsigned char类型。
简而言之,给定一个对象,只允许您通过具有列表中某种类型的表达式访问该对象。对于没有基类的类类型对象,如std::vector<T>
,基本上只限于第一个,第二个和最后一个项目符号中指定的类型。
std::vector<Base*>
和std::vector<Derived*>
是完全不相关的类型,您不能使用std::vector<Base*>
类型的对象,就好像它是std::vector<Derived*>
一样。如果您违反此规则,编译器可以执行各种操作,包括:
对其中一种进行不同的优化,或
以不同方式列出内部成员,或
执行优化,假设std::vector<Base*>*
永远不能引用与std::vector<Derived*>*
相同的对象
使用运行时检查确保您没有违反严格别名规则
[它也可能不做这些事情并且可能“有效”,但不能保证它会“正常工作”,如果你改变编译器或编译器版本或编译设置,它可能都会停止“工作”。我在这里使用吓唬报价是有原因的。 : - )]
即使您只有Base*[N]
,也无法使用该数组,就好像它是Derived*[N]
一样(尽管在这种情况下,使用可能更安全,“更安全”意味着“仍然”未定义,但不太可能让你陷入麻烦)。
答案 1 :(得分:4)
你正在调用reinterpret_cast&lt;&gt;的坏精神。
除非你真的知道你做了什么(我的意思不是骄傲而不是迂腐),reinterpret_cast是邪恶之门。
我所知道的唯一安全用途是管理C ++和C函数调用之间的类和结构。 可能还有其他一些。
答案 2 :(得分:3)
容器中协方差的一般问题如下:
假设您的演员阵容有效并且合法(不是,但我们假设它适用于以下示例):
#include <vector>
struct A {};
struct B : A { public: int Method(int x, int z); };
struct C : A { public: bool Method(char y); };
std::vector<B*> test;
std::vector<A*>* foo = reinterpret_cast<std::vector<A*>*>(&test);
foo->push_back(new C);
test[0]->Method(7, 99); // What should happen here???
所以你也把C *重新诠释为B * ......
实际上我不知道.NET和Java如何管理它(我认为它们在尝试插入C时会抛出异常)。
答案 3 :(得分:2)
我认为展示比告诉更容易:
struct A { int a; };
struct Stranger { int a; };
struct B: Stranger, A {};
int main(int argc, char* argv[])
{
B someObject;
B* b = &someObject;
A* correct = b;
A* incorrect = reinterpret_cast<A*>(b);
assert(correct != incorrect); // troubling, isn't it ?
return 0;
}
此处显示的(特定)问题是,在进行“正确”转换时,编译器会根据对象的内存布局添加一些指针ajdustement。在reinterpret_cast
上,不会执行任何调整。
我想你会理解为什么reinterpet_cast