簡體   English   中英

比較 C++20 中的多態類型

[英]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;
}

https://godbolt.org/z/er4fsK

我們現在正在遷移到 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實際上不是標識符,因為它不標識任何東西。 這是一個破碎的抽象。

一種更直觀的表達方式可能是:類型是使標識符有意義的唯一因素。 除非先將其向下轉換為MachineIdentifierUserIdentifier ,否則您無法使用裸Identifier做任何事情。 因此, Identifier class 很可能是錯誤的,將MachineIdentifierUserIdentifier進行比較是編譯器應該檢測到的類型錯誤。

在我看來, Identifier存在的最可能原因是因為有人意識到MachineIdentifierUserIdentifier之間存在公共代碼,並得出結論,公共行為應該提取到Identifier基類型,特定類型繼承自它。 對於在學校學習過“繼承使代碼重用”但尚未意識到還有其他類型的代碼重用的人來說,這是一個可以理解的錯誤。

他們應該寫什么呢? 模板怎么樣? 模板實例不是模板的子類型,也不是彼此的子類型。 如果您有這些標識符代表的類型MachineUser ,您可以嘗試編寫一個模板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;
  }
};

現在,將MachineIdentifierUserIdentifier進行比較是一個編譯時錯誤。

這種技術稱為奇怪的重復模板模式(另請參閱 )。 當您第一次遇到它時有點莫名其妙,但它讓您能夠引用超類中的特定子類類型(在本例中為Id )。 它對您來說也可能是一個不錯的選擇,因為與大多數其他選項相比,它需要對已經正確使用MachineIdentifierUserIdentifier的代碼進行相對較少的更改。


¹如果標識符可互換的,那么這個答案的大部分(以及其他大多數答案)可能不適用。 但如果是這樣的話,也應該可以不向下轉換地進行比較。

您的代碼中沒有任何多態性。 您可以使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM