我有介于c++17和c++20之间的代码,具体来说,我们在GCC-9和clang-9上启用了c++20,在这两种情况下,只部分实现了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目前所拥有的)。
由于实现了有关比较运算符的新规则,上述代码停止编译。可逆operator==会引起歧义。我不能使用宇宙飞船操作符,因为它不是在版本9中实现的,但我从示例中忽略了这一点--我假设任何对==起作用的操作都会与其他操作符一起工作。
那么:在多态类型的c++20中实现比较操作符的推荐方法是什么?
发布于 2020-10-26 21:13:53
作为中间解决方案,您可以将您的多态等式operator==重新分解为基类中定义的非虚拟operator==,该类多形地分配给非运算符虚拟成员函数:
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虚拟成员函数的分派将始终在operator==的左侧参数上完成。
const bool result = (user == machine); // user.isEqual(machine);发布于 2020-10-27 09:10:54
好的,我看到@dfrib给出的答案中没有提到它,所以我将扩展这个答案来显示它。
您可以在Identifier结构中添加一个抽象(纯虚拟)函数,该函数返回其“标识”。
然后,在扩展Identifier结构的每个结构中,您可以调用该函数,而不是动态转换输入对象并检查其类型是否与this对象匹配。
当然,您必须确保完全区分每个结构的标识集。换句话说,任何两组标识都不能共享任何公共值(即,这两个集合必须是不相交的)。
这将允许您完全摆脱RTTI,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检查,后者比较了输入对象的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;
}
};发布于 2020-10-27 23:23:04
这看起来不像是多态问题。实际上,我认为任何多态性都是数据模型错误的症状。
如果您有标识机器的值和标识用户的值,并且这些标识符不可互换,则它们不应该共享超级类型。“是一个标识符”的属性是关于如何在数据模型中使用该类型来识别另一个类型的值的事实。MachineIdentifier是标识符,因为它标识机器;UserIdentifier是标识符,因为它标识用户。但是Identifier实际上不是一个标识符,因为它不识别任何东西!这是一个破碎的抽象。
更直观的方式可能是:类型是唯一使标识符有意义的东西。除非您首先将其降为Identifier或UserIdentifier,否则不能使用普通的UserIdentifier进行任何操作。因此,拥有一个Identifier类很可能是错误的,将一个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概念来实现这一点,或者类似的方法,但是,让我们将模板与继承结合起来,以获得这两种概念的一些优点:
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的代码进行相对较少的更改。
如果标识符是可互换的,则大多数这一答复(和大多数其他答复)可能不适用。但如果是这样的话,也应该有可能对它们进行比较而不进行降级。
https://stackoverflow.com/questions/64544936
复制相似问题