C ++中的容器协方差

时间:2011-01-26 17:19:22

标签: c++ covariance

我知道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完全合法。

那么标准中的不良精神是什么(风格除外)?

4 个答案:

答案 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