2009-07-13 50 views
2

在這被標記爲重複之前,我知道this的問題,但在我的情況下,我們正在談論const容器。爲什麼不可能將常量集<Derived*>作爲常量集<Base*>傳遞給函數?

我有2類:

class Base { }; 
class Derived : public Base { }; 

和一個功能:

void register_objects(const std::set<Base*> &objects) {} 

我想調用這個函數爲:

std::set<Derived*> objs; 
register_objects(objs); 

編譯器不接受這種說法。爲什麼不?該集合是不可修改的,因此不存在將非Derived對象插入到其中的風險。我怎樣才能以最好的方式做到這一點?

編輯:
據我所知,現在的編譯器的工作方式是set<Base*>set<Derived*>是完全無關的,並且因而沒有找到函數簽名。然而,我現在的問題是:爲什麼編譯器會像這樣工作?會不會有任何異議看不到const set<Derived*>const set<Base*>

回答

13

編譯器不接受這個的原因是標準告訴它不要。

標準告訴它不要的原因是委員會沒有要介紹的規則const MyTemplate<Derived*>是與const MyTemplate<Base*>相關的類型,即使非常量類型不相關。他們當然不希望爲std :: set設置一個特殊的規則,因爲通常這種語言不會爲庫類創建特殊的情況。

標準委員會不想讓這些類型相關的原因是MyTemplate可能沒有容器的語義。試想一下:

template <typename T> 
struct MyTemplate { 
    T *ptr; 
}; 

template<> 
struct MyTemplate<Derived*> { 
    int a; 
    void foo(); 
}; 

template<> 
struct MyTemplate<Base*> { 
    std::set<double> b; 
    void bar(); 
}; 

那麼,什麼是它甚至意味着傳遞一個const MyTemplate<Derived*>作爲const MyTemplate<Base*>?這兩個類沒有共同的成員函數,並且不兼容佈局。你需要在兩者之間有一個轉換運算符,否則編譯器不知道要做什麼,無論它們是否是const。但是在標準中定義模板的方式,即使沒有模板專業化,編譯器也不知道該做什麼。

std::set本身可以提供一個轉換操作符,但是這隻需要創建一個副本(*),您可以輕鬆地完成自己的操作。如果有這樣的東西,如std::immutable_set,那麼我認爲有可能實現這樣的一個std::immutable_set<Base*>可以通過指向相同的pImpl從std::immutable_set<Derived*>構建。即便如此,如果派生類中有非虛擬運算符重載 - 基本容器將調用基本版本,那麼奇怪的事情將會發生,因此如果轉換可能會對該集合進行排序,前提是它有一個非默認比較器對象本身而不是地址。所以轉換會帶來沉重的警告。但無論如何,並不存在immutable_set,而const與不可變的不一樣。

此外,假設Derived通過虛擬或多重繼承與Base有關。然後,您不能重新解釋Derived的地址作爲Base的地址:在大多數實現中,隱式轉換會更改地址。因此,您不能將包含Derived*的結構批量轉換爲包含Base*的結構而不復制結構。但是C++標準實際上允許這發生在任何非POD類中,而不僅僅是多重繼承。而且Derived是非POD,因爲它有一個基類。所以爲了支持std::set的這種變化,繼承和結構佈局的基本原理將不得不改變。這是C++語言的一個基本限制,即標準容器不能以您想要的方式重新解釋,而且我也沒有意識到可以在不降低效率或可移植性的情況下實現它們的任何技巧。這很令人沮喪,但這件事很難。

由於你的代碼是按值傳遞反正一組,你可以只讓該副本:

std::set<Derived*> objs; 
register_objects(std::set<Base*>(objs.begin(), objs.end()); 

[編輯:你已經改變了你的代碼示例不按值傳遞。我的代碼仍然有效,而且據我所知是你可以做其他的比重構調用代碼首先使用std::set<Base*>最好的。]

寫作std::set<Base*>的包裝,以確保所有的元素都是Derived*,順便Java泛型工作,比安排想要高效轉換更容易。所以,你可以這樣做:

template<typename T, typename U> 
struct MySetWrapper { 
    // Requirement: std::less is consistent. The default probably is, 
    // but for all we know there are specializations which aren't. 
    // User beware. 
    std::set<T> content; 
    void insert(U value) { content.insert(value); } 
    // might need a lot more methods, and for the above to return the right 
    // type, depending how else objs is used. 
}; 

MySetWrapper<Base*,Derived*> objs; 
// insert lots of values 
register_objects(objs.content); 

(*)其實,我想這可能寫入時複製,這在典型的方式使用常量參數的情況下,就意味着它永遠不會需要做的複製。但是,寫入時複製在STL實現中有點不受信任,即使這不是我懷疑委員會會要求這樣一個重量級的實現細節。

0

好衍生物,在你提到的問題,set<Base*>set<Derived*>是不同的對象表示。您的register_objects()函數需要一個set<Base*>對象。所以編譯器不知道任何需要set<Derived*>的register_objects()。參數的常量不會改變任何內容。在引用的問題中提到的解決方案似乎是你可以做的最好的事情。取決於你需要做什麼...

2

std::set<Base*>std::set<Derived*>基本上是兩個不同的對象。雖然Base和Derived類通過繼承關聯,但在編譯器模板實例化級別,它們是兩個不同的實例化(set)。

1

首先,這似乎有點奇怪,你是不是通過引用傳遞...

其次,在其他文章中提到,你會過得更好創建傳入的設置爲標準: :設置< Base *>,然後爲每個成員創建一個Derived類。

你的問題肯定是由於這兩種類型完全不同而引起的。 std :: set <就編譯器而言,Derived *>絕不會從std :: set < Base *>繼承。它們只是兩種不同類型的集合...

+0

我糾正了參考通過,這是我的問題中的一個錯字,在我的代碼中,我做了這個正確的。 – 2009-07-13 11:05:20

2

如果您register_objects函數接收一個參數,它可以把/期待任何Base子在那裏。這就是它的簽名sais。

這是對Liskov替代原則的違反。

這個特殊問題也被稱爲Covariance。在這種情況下,你的函數參數是一個常量容器,它可以使工作。在參數容器可變的情況下,它不能工作。

2

先看看這裏:Is array of derived same as array of base。在你的情況下,派生集合是一組完全不同的容器,並且由於沒有隱式轉換運算符可用於在它們之間進行轉換,所以編譯器提供了一個錯誤。

0

如您所知,一旦刪除了非const操作,這兩個類就非常相似。但是,在C++中,繼承是類型的屬性,而const僅僅是類型之上的限定符。這意味着你不能正確指出const Xconst Y派生,即使X的Y.

派生

此外,如果X不Y的繼承,適用於X和Y的所有CV-合格的變種以及。這延伸到std::set實例化。由於std::set<Foo>不會從std::set<bar>繼承,因此std::set<Foo> const也不會從std::set<bar> const繼承。

相關問題