2014-05-11 41 views
0

全部。std :: map不刪除重複的自定義對象

所以,我創建了一個可比較的基類(la Java),它在C++中完成重載比較運算符的工作。它是:

template <class T> 
class Comparable { 
public: 
    bool operator== (const T& rhs) const { return this->compare(rhs) == 0; } 
    bool operator!= (const T& rhs) const { return !(*this == rhs); } 
    bool operator< (const T& rhs) const { return this->compare(rhs) < 0; } 
    bool operator<= (const T& rhs) const { return (*this == rhs) || (*this < rhs); } 
    bool operator> (const T& rhs) const { return this->compare(rhs) > 0; } 
    bool operator>= (const T& rhs) const { return (*this == rhs) || (*this > rhs); } 
protected: 
    virtual int compare (const T& rhs) const = 0; 
}; 

子類對象有一個ID和需要作爲排序鍵的數據。我已經實現了每個ID,使得具有相同ID的對象返回0,並且如果它們不是相同的ID,則根據它們的數據進行排序。這裏有一個例子:

int Foo::compare(const Foo& rhs) const 
{ 
    if (_id == rhs._id) 
     return 0; 
    // _value is integer data; comparison should sort on this 
    if (_value == rhs._value) 
    { 
     // OK, same data, now sort on ID's 
     return _id.compare(rhs._id); 
    } 
    // Sort on value 
    return _value - rhs._value; 
} 

到目前爲止,我認爲這麼好。

但是,當我嘗試將Foo對象存儲在std :: set容器中時,該設置不會消除重複項。也就是說,其中仍然會有對象包含相同的ID,即使這應該被視爲相同。

有沒有人有任何想法是怎麼回事?

編輯:有很多關於爲什麼代碼是這樣設計的問題。

有兩個條件我需要滿足:

  1. 對象具有相同的ID必須被認爲是「相同的」對象,而不管值。
  2. 不具有相同ID的對象必須根據它們的值進行排序(該值是什麼,取決於對象的類型)。

這是因爲創建這些對象的數據來自未經驗證的源。如果該源提供的數據具有相同的ID但數據不同,則這是無效的數據。

編輯2:關於std :: map的嚴格弱排序。假設您有兩個Foo對象A和B.

如果它們的ID相同,則A < B爲假,而B < A爲假。 如果他們的ID的不相等,則A < B是真的還是假的,這取決於它們的值,和B < A是甲< B.

相反如果這不能滿足嚴格的排序規則?

在任何情況下,如果std :: set使用小於運算符(因爲它默認情況下),它不應該按照設計工作嗎?編號3:std::set,而不是std::map。這真是我的愚蠢。道歉。

+2

您的基類缺少虛擬析構函數。 – chris

+1

if(_id == rhs._id) return 0; 這看起來很奇怪 - 這意味着它表示不管值如何都是平等的,但如果「值」相等,則稍後處理ID比較。哪一個? – Joe

+0

此外,這是如何區分具有相同ID但不同值的數據與具有不同ID排序的數據?在這方面你的函數是不明確的(我認爲,你不會顯示_id.compare),那麼2個具有相同ID的值相差2的項和2個ID相差2的項將被視爲相等。 – Joe

回答

4

std::map不確定通過operator==重複。它使用operator< ,因爲無論如何它需要使用它來確定順序。您的operator<已損壞。它需要執行strict weak ordering

比較失敗的方式在以下屬性(稱爲傳遞性)中。對於3個對象,如果A < BB < C,則必須是A < C

因此,考慮三個對象,ABCA.id != B.id,但是A.value < B.value,所以A < B。現在,C.id == B.id(因此C.id != A.id),但C.value < A.value。所以C < AA < B。因此,C應該是< B。但它不是,因爲C.id == B.id。要做到這一點是

一種方式來定義你的compare功能是這樣的:

int Foo::compare(const Foo& rhs) const 
{ 
    if (_id < rhs._id) 
     return -1; 
    if (rhs._id < _id) 
     return 1; 

    return _value - rhs._value; 
} 

如果不能使用,你不能找到一些其他的方式來強制執行正確排序,那麼你根本無法使用您的對象作爲std::map中的鍵。

1.如果rhs不小於lhs,且lhs不小於rhs,則暗示它們相等。你也可以提供一個替代函數,只要它也執行嚴格的弱排序。

+0

這將按ID排序,如果這些ID相同,將只按值排序。這與我所要求的完全相反。 –

+2

@KarlGiesing:這只是一個例子。關鍵是你必須想出一種方法來爲你的對象定義一個嚴格的弱排序。如果你無法做到這一點,那麼你不能使用'std :: map'。 –

+1

傳遞性要求正是問題所在。剛剛確認了它。謝謝。 –

3

如果您沒有嚴格的弱排序,您將無法使用map,如果您這樣做並且無法正常工作,則無法投訴。這是map的先決條件。

編輯:

compare沒有實現指定的語義 - 你應該返回0,如果_value == rhs._value在你的第二個測試。

但是,通過這種修復方法,您仍然沒有一致的排序順序 - 請參閱Benjamin Lindley在評論中給出的示例。從本質上講,您的排序沒有意義 - 您需要修復語義,或停止使用需要一致排序的標準庫組件。

+1

這是不夠的。考慮三個對象,'A','B'和'C'。 'A.id!= B.id',但是A.value

+0

@Benjamin謝謝。我正在努力想出一個反例。我會更新。 –

+0

@BenjaminLindley:這對我來說真的很有意義,我現在明白了。讓它成爲答案,你會得到我的投票。 –