我有一些基本类型的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 ==
}
答案 0 :(得分:10)
使用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中的演员相比”那么慢),因为它也会分析类用于处理继承的层次结构(以及多重继承)。你不需要在这里处理。
答案 1 :(得分:4)
当然可以在不使用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);
}
答案 2 :(得分:1)
这正是RTTI的用途。在编译时,你只知道它是Shape&
,所以你只需要进行运行时检查,看看它实际上是什么派生类型,然后才能进行有意义的比较。在不违反多态性的情况下,我不知道有任何其他方法可以做到这一点。
你可以为operator ==
为不同的派生类型组合定义许多自由函数,但它不会有多态行为,因为你可能通过Shape&
指针处理这些函数,所以即使调用代码也没有实际上知道对象的类型。
因此,RTTI在这里(几乎)是不可避免的,事实上这种情况正是RTTI存在的原因。在某些情况下,它只被认为是不好的做法,因为它增加了一定的脆弱性(当事情不属于你知道如何处理的类型时,你必须确保处理,因为任何人都可以出现并创建{{的新子类1}}),它增加了运行时成本。但是,您已经通过使用虚拟方法支付了运行时成本。
我说'几乎不可避免',因为你可能会编造一些系统,该系统对传递给Shape
的对象进行进一步的虚方法调用,以获得正确的比较行为,但实际上是另一种虚方法查找(记住,虚拟方法也有运行时性能损失,因为编译器不知道将调用哪个实现,因此无法放入具体的函数地址)可能不会比RTTI的成本更快。
如果有人知道如何在没有这笔费用的情况下做到这一点,我很乐意看到它。
答案 3 :(得分:1)
我的感觉是,当你正在深入研究对象的内部表征时,存在对Liskov替换原则的根本违反。但是,如果您很乐意公开对象的内部表示(或者由于其他原因必须这样做),那么这样的事情就可以了。
class Shape
{
virtual void std::string serialize() const =0;
bool operator==( const Shape & s )
{
return this.serialize() == s.serialize();
}
};