2011-01-26 38 views
16

我知道C++不支持容器元素的協方差,就像在Java或C#中一樣。所以,可能是下面的代碼是未定義的行爲: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是完全合法的。

那麼標準中有什麼不好的精神讓我想起(風格除外)?

+1

使用該點沒有後的reinterpret_cast <>()的定義。它可能有效,但你的條件列表非常短。我會再添加一些前提條件。 sizeof(A)== sizeof(B); A或B都不能包含任何類型的虛擬功能。 A和B以及放置在數組中的任何後代都不能使用多重繼承。 – 2011-01-26 17:28:03

+7

非C++的具體答案是它不是類型安全的。如果您將「A」添加到foo中,則您的測試處於無效狀態,因爲它確保所有元素均爲「B」類型。而C#也不支持這個。 C#僅支持以安全方式(僅用於輸入或輸出)且僅用於接口和委託的通用參數。 Java支持它,因爲它增加了運行時檢查,並在內部工作在對象基類上。 – CodesInChaos 2011-01-26 17:30:06

+0

這個問題看起來類似於http://stackoverflow.com/questions/842387/how-do-i-dynamically-cast-between-vectors-of-pointers – Nekuromento 2011-01-26 17:36:39

回答

16

這裏違反該規則記錄在C++ 03 3.10/15 [basic.lval],它指定所謂非正式地稱爲「嚴格別名規則」

如果程序試圖訪問通過其他的左值比以下類型中的一個對象的所存儲的值的行爲是未定義:

  • 動態類型的對象,

  • 的cv-合格版本動態型的對象,的

  • 一種類型,是有符號或對應的動態對象的類型無符號類型,

  • 一種類型,是有符號或對應於CV-無符號類型動態類型的對象的合格版本,

  • 的聚集體或聯合類型包括其成員之間的上述類型的一個(包括遞歸地,一個子聚集或含有聯合的成員),

  • 一個類型是(possi bly cv-qualified)基類動態類型的對象,

  • 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](雖然在這種情況下,使用很可能會更安全,這裏的「安全」是指「仍然不確定,但少可能讓你陷入困境)。

4

要調用的reinterpret_cast <的不良精神>。

除非你真的知道你做了什麼(我指的不是驕傲,而不是迂腐)的reinterpret_cast是一個大門邪惡的。

唯一的安全使用我所知道的是管理類es和C++和C函數調用之間的結構。 但也有其他人。

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應該通常從代碼禁止...

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,當他們拋出一個異常)。