[英]Comparing polymorphic types in c++20
我的代碼介於 c++17 和 c++20 之間。 具體來說,我們在 GCC-9 和 clang-9 上啟用了 c++20,它只是部分實現。
在代碼中,我們有相當大的多態類型層次結構,如下所示:
struct Identifier {
virtual bool operator==(const Identifier&other) const = 0;
};
struct UserIdentifier : public Identifier {
int userId =0;
bool operator==(const Identifier&other) const override {
const UserIdentifier *otherUser = dynamic_cast<const UserIdentifier*>(&other);
return otherUser && otherUser->userId == userId;
}
};
struct MachineIdentifier : public Identifier {
int machineId =0;
bool operator==(const Identifier&other) const override {
const MachineIdentifier *otherMachine = dynamic_cast<const MachineIdentifier*>(&other);
return otherMachine && otherMachine->machineId == machineId;
}
};
int main() {
UserIdentifier user;
MachineIdentifier machine;
return user==machine? 1: 0;
}
我們現在正在遷移到 GCC-10 和 clang-10,但由於某些原因我們仍然需要在版本 9 上工作(好吧,至少是 clang-9,因為這是 android NDK 目前擁有的)。
上面的代碼停止編譯,因為實施了關於比較運算符的新規則。 可逆運算符== 會導致歧義。 我不能使用宇宙飛船運算符,因為它沒有在版本 9 中實現。但是我在示例中省略了這一點——我假設任何與 == 一起工作的東西都將與其他運算符一起工作。
那么:在 C++20 中使用多態類型實現比較運算符的推薦方法是什么?
作為中間解決方案,您可以將多態相等operator==
符== 重構為基 class 中定義的非虛擬operator==
,它以多態方式分派給非運算符虛擬成員 function:
struct Identifier {
bool operator==(const Identifier& other) const {
return isEqual(other);
}
private:
virtual bool isEqual(const Identifier& other) const = 0;
};
// Note: do not derive this class further (less dyncasts may logically fail).
struct UserIdentifier final : public Identifier {
int userId = 0;
private:
virtual bool isEqual(const Identifier& other) const override {
const UserIdentifier *otherUser = dynamic_cast<const UserIdentifier*>(&other);
return otherUser && otherUser->userId == userId;
}
};
// Note: do not derive this class further (less dyncasts may logically fail).
struct MachineIdentifier final : public Identifier {
int machineId = 0;
private:
virtual bool isEqual(const Identifier& other) const override {
const MachineIdentifier *otherMachine = dynamic_cast<const MachineIdentifier*>(&other);
return otherMachine && otherMachine->machineId == machineId;
}
};
現在將不再有歧義,因為對isEqual
虛擬成員 function 的調度將始終在operator==
的左側參數上完成。
const bool result = (user == machine); // user.isEqual(machine);
好的,我看到@dfrib 給出的答案中沒有提到它,所以我將擴展該答案以顯示它。
您可以在Identifier
結構中添加一個抽象(純虛擬)function,它返回其“身份”。
然后,在擴展Identifier
結構的每個結構中,您可以調用 function 而不是動態轉換輸入 object 並檢查其類型是否與this
object 匹配。
當然,您必須確保完全區分每個結構的標識集。 換句話說,任何兩組身份都不能共享任何共同的值(即,這兩組必須不相交)。
這將允許您完全擺脫 RTTI,這幾乎與多態 IMO 完全相反,並且還會產生額外的運行時影響。
這是該答案的擴展:
struct Identifier {
bool operator==(const Identifier& other) const {
return getVal() == other.getVal();
}
private:
virtual int getVal() const = 0;
};
struct UserIdentifier : public Identifier {
private:
int userId = 0;
virtual int getVal() const override {
return userId;
}
};
struct MachineIdentifier : public Identifier {
private:
int machineId = 100;
virtual int getVal() const override {
return machineId;
}
};
如果您想支持具有除int
以外的某種類型的標識符的結構,那么您可以擴展此解決方案以使用模板。
除了為每個結構強制執行一組不同的身份之外,您還可以添加一個type
字段,並確保只有該字段在不同的結構中是唯一的。
本質上,這些類型等同於dynamic_cast
檢查,它比較輸入object的 V 表指針和輸入結構的 V 表指針(因此我認為這種方法是完整的與多態相反)。
這是修改后的答案:
struct Identifier {
bool operator==(const Identifier& other) const {
return getType() == other.getType() && getVal() == other.getVal();
}
private:
virtual int getType() const = 0;
virtual int getVal() const = 0;
};
struct UserIdentifier : public Identifier {
private:
int userId = 0;
virtual int getType() const override {
return 1;
virtual int getVal() const override {
return userId;
}
};
struct MachineIdentifier : public Identifier {
private:
int machineId = 0;
virtual int getType() const override {
return 2;
virtual int getVal() const override {
return machineId;
}
};
這看起來不像是多態性的問題。 實際上,我認為存在任何多態性都是數據 model 錯誤的症狀。
如果您有標識機器的值和標識用戶的值,並且這些標識符不可互換¹,則它們不應共享超類型。 “作為標識符”的屬性是關於如何在數據 model 中使用該類型來標識另一種類型的值的事實。 MachineIdentifier
是一個標識符,因為它標識一台機器; UserIdentifier
是標識符,因為它標識用戶。 但Identifier
實際上不是標識符,因為它不標識任何東西。 這是一個破碎的抽象。
一種更直觀的表達方式可能是:類型是使標識符有意義的唯一因素。 除非先將其向下轉換為MachineIdentifier
或UserIdentifier
,否則您無法使用裸Identifier
做任何事情。 因此, Identifier
class 很可能是錯誤的,將MachineIdentifier
與UserIdentifier
進行比較是編譯器應該檢測到的類型錯誤。
在我看來, Identifier
存在的最可能原因是因為有人意識到MachineIdentifier
和UserIdentifier
之間存在公共代碼,並得出結論,公共行為應該提取到Identifier
基類型,特定類型繼承自它。 對於在學校學習過“繼承使代碼重用”但尚未意識到還有其他類型的代碼重用的人來說,這是一個可以理解的錯誤。
他們應該寫什么呢? 模板怎么樣? 模板實例不是模板的子類型,也不是彼此的子類型。 如果您有這些標識符代表的類型Machine
和User
,您可以嘗試編寫一個模板Identifier
結構並對其進行特殊化,而不是對其進行子類化:
template <typename T>
struct Identifier {};
template <>
struct Identifier<User> {
int userId = 0;
bool operator==(const Identifier<User> &other) const {
return other.userId == userId;
}
};
template <>
struct Identifier<Machine> {
int machineId = 0;
bool operator==(const Identifier<Machine> &other) const {
return other.machineId == machineId;
}
};
當您可以將所有數據和行為移動到模板中而無需專門化時,這可能最有意義。 否則,這不一定是最佳選擇,因為您無法指定Identifier
實例化必須實現operator==
。 我認為可能有一種方法可以使用 C++20 概念來實現這一點或類似的東西,但相反,讓我們將模板與 inheritance 結合起來以獲得兩者的一些優勢:
template <typename Id>
struct Identifier {
virtual bool operator==(const Id &other) const = 0;
};
struct UserIdentifier : public Identifier<UserIdentifier> {
int userId = 0;
bool operator==(const UserIdentifier &other) const override {
return other.userId == userId;
}
};
struct MachineIdentifier : public Identifier<MachineIdentifier> {
int machineId = 0;
bool operator==(const MachineIdentifier &other) const override {
return other.machineId == machineId;
}
};
現在,將MachineIdentifier
與UserIdentifier
進行比較是一個編譯時錯誤。
這種技術稱為奇怪的重復模板模式(另請參閱crtp )。 當您第一次遇到它時有點莫名其妙,但它讓您能夠引用超類中的特定子類類型(在本例中為Id
)。 它對您來說也可能是一個不錯的選擇,因為與大多數其他選項相比,它需要對已經正確使用MachineIdentifier
和UserIdentifier
的代碼進行相對較少的更改。
¹如果標識符是可互換的,那么這個答案的大部分(以及其他大多數答案)可能不適用。 但如果是這樣的話,也應該可以不向下轉換地進行比較。
您的代碼中沒有任何多態性。 您可以使用Identifier
指針或引用來強制動態綁定比較運算符 function(多態性)。
例如,而不是
UserIdentifier user;
MachineIdentifier machine;
return user==machine? 1: 0;
有了參考資料,你可以這樣做:
UserIdentifier user;
MachineIdentifier machine;
Identifier &iUser = user;
return iUser == machine ? 1: 0;
相反,您可以顯式調用UserIdentifier
的比較運算符:
return user.operator==(machine) ? 1: 0;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.