比较没有RTTI的C++中的多态碱基类型
Comparing Polymorphic Base Types in C++ without RTTI
我有一些指向基本类型的 Shape 的指针。我想使用 == 运算符比较这些对象。如果对象是不同的派生类型,则 == 运算符显然应返回 false。但是,如果它们属于相同的派生类型,则应比较派生类型的成员。
我读到使用C++RTTI是一种不好的做法,只能在极少数和必要的情况下使用。据我所知,如果不使用 RTTI,这个问题通常无法解决。每个重载的 == 运算符都必须检查 typeid,如果它们相同,则执行dynamic_cast并比较成员。这似乎是一个共同的需求。这个问题有某种成语吗?
#include <iostream>
using namespace std;
class Shape {
public:
Shape() {}
virtual ~Shape() {}
virtual void draw() = 0;
virtual bool operator == (const Shape &Other) const = 0;
};
class Circle : public Shape {
public:
Circle() {}
virtual ~Circle() {}
virtual void draw() { cout << "Circle"; }
virtual bool operator == (const Shape &Other) const {
// If Shape is a Circle then compare radii
}
private:
int radius;
};
class Rectangle : public Shape {
public:
Rectangle() {}
virtual ~Rectangle() {}
virtual void draw() { cout << "Rectangle"; }
virtual bool operator == (const Shape &Other) const {
// If Shape is a Rectangle then compare width and height
}
private:
int width;
int height;
};
int main() {
Circle circle;
Rectangle rectangle;
Shape *Shape1 = &circle;
Shape *Shape2 = &rectangle;
(*Shape1) == (*Shape2); // Calls Circle ==
(*Shape2) == (*Shape1); // Calls Rectangle ==
}
使用 RTTI。使用 typeid
,但使用 static_cast
而不是 dynamic_cast
。
从设计的角度来看,我想说这正是RTTI的目的,任何替代解决方案都必然会更丑陋。
virtual bool operator == (const Shape &Other) const {
if(typeid(Other) == typeid(*this))
{
const Circle& other = static_cast<const Circle&>(Other);
// ...
}
else
return false;
}
从性能的角度来看: typeid
往往很便宜,只需查找存储在虚拟表中的指针即可。您可以廉价地比较动态类型的相等性。
然后,一旦您知道您拥有正确的类型,您就可以安全地使用 static_cast
.
dynamic_cast
以慢而闻名(即像"与虚拟函数调用相比"那样慢,而不是像"与 Java 中的强制转换相比"那样慢),因为它还会分析类层次结构来处理继承(以及多重继承)。你不需要在这里处理这个问题。
当然可以在不使用typeid
和强制转换的情况下完成。但这有点麻烦,所以你必须决定它是否值得做。
版本一 - 双重访客
使用访客模式
class ShapeVisitor
{
public:
virtual void visitCircle(Circle const &) = 0;
virtual void visitRectangle(Rectangle const &) = 0;
// other shapes
}
到类Shape
添加
virtual void acceptVisitor(ShapeVisitor &) = 0;
和访客
class CircleComparingVisitor : public ShapeVisitor
{
Circle const & lhs; // shorthand for left hand side
bool equal; // result of comparison
public:
CircleComparingVisitor(Circle const & circle):lhs(circle), equal(false){}
virtual void visitCircle(Circle const & rhs) {equal = lhs.radius == rhs.radius;}
virtual void visitRectangle(Rectangle const &) {}
// other shapes
bool isEqual() const {return equal;}
}
// other shapes analogically
class ShapeComparingVisitor
{
Shape const & rhs; // right hand side
bool equal;
public:
ShapeComparingVisitor(Shape const & rhs):rhs(rhs), equal(false) {}
bool isEqual() const {return equal;}
virtual void visitCircle(Circle const & lhs)
{
CircleComparingVisitor visitor(lhs);
rhs.accept(visitor);
equal = visitor.isEqual();
}
virtual void visitRectangle(Rectangle const & lhs)
{
RectangleComparingVisitor visitor(lhs);
rhs.accept(visitor);
equal = visitor.isEqual();
}
}
最后operator==
不需要虚拟
bool Shape::operator==(const Shape &rhs) const
{
ShapeComparingVisitor visitor(rhs);
this->accept(visitor);
return visitor->isEqual();
}
第二个想法 - operator==
可能是虚拟的,并使用适当的比较访问者 - 所以你可以摆脱ShapeComparingVisitor
版本二 - 双重调度
您添加到Shape
virtual bool compareToCircle(Circle const &) const == 0;
virtual bool compareToRectangle(Rectangle const &) const == 0;
并以特定形状实施
现在以现在为例
bool Circle::operator==(Shape const & rhs) const
{
return rhs.compareToCircle(*this);
}
这正是RTTI的用途。在编译时,你只知道它是一个Shape&
,所以你只需要做一个运行时检查,看看它实际上是什么派生类型,然后才能进行有意义的比较。我不知道在不违反多态性的情况下还有其他方法可以做到这一点。
您可以为不同的派生类型组合定义许多用于operator ==
的自由函数,但它不会具有多态行为,因为您可能通过Shape&
指针处理这些函数,因此即使调用代码实际上也不知道对象是什么类型。
因此,RTTI在这里(几乎)是不可避免的,事实上,这种情况正是RTTI存在的原因。在某些情况下,它只被认为是不好的做法,因为它增加了一定的脆弱性(你必须确保当事情不属于你知道如何处理的类型时,你处理,因为任何人都可以出现并创建一个新的Shape
子类),并且它增加了运行时成本。但是,您已经通过使用虚拟方法支付了运行时成本。
我说"几乎不可避免的"是因为您可能会炮制一些系统,该系统对传递给operator ==
的对象进行进一步的虚拟方法调用以获得正确的比较行为,但实际上,另一种虚拟方法查找(请记住,虚拟方法也有运行时性能损失,因为编译器不知道将调用哪个实现,因此无法输入具体的函数地址)可能不比RTTI的成本。
如果有人知道一种无需成本的方法,我很想看到它。
我的感觉是,这里从根本上违反了Liskov替换原则,因为你正在挖掘对象的内部表示。但是,如果您乐于公开对象的内部表示形式(或者出于其他原因必须这样做),那么这样的事情将起作用。
class Shape
{
virtual void std::string serialize() const =0;
bool operator==( const Shape & s )
{
return this.serialize() == s.serialize();
}
};
- 一个Q_PROPERTY可以泛化为多个基类吗?
- C++ 多态模板类,模板类型对象的实例化
- 非虚拟基的多态成员类
- 如何访问"std::variant"的任何子级的"多态"基类?
- 多态unique_ptr类成员
- 多个基类可以具有相同的虚拟方法吗?
- 如何在代码块 IDE 上修复此警告,警告:按值 [-Wcatch-value=] 捕获多态类型"类 std::d omain_error"
- 流运算符和多态基类列表
- 如何查找多态基类向量中包含的对象的类型?
- 使用对象切片可靠地从多个基类之一中复制
- 增强序列化多态性类
- 是否可以将多态性类存储在共享内存中
- 有没有办法破坏然后移动构造一个多态基类
- 动态强制转换不适用于非多态基类
- 非多态派生类的基类
- 如何通过多态基类接口使用模板化的子类
- 使用多态基类(包含虚函数)访问数组元素时的类型
- 从多态基指针调用基类方法很慢
- 非多态基类中的析构函数
- 如何从指向多态基类的指针复制/创建派生类实例