!= 运算符应该在 class 层次结构中的什么地方定义?
Where should != operator be defined in a class hierarchy?
这是一个非常简单的 class 层次结构:
class A
{
public:
A( int _a ) : a( _a ) {}
virtual bool operator==( const A& right ) const
{
return a == right.a;
}
virtual bool operator!=( const A& right ) const
{
return !( *this == right );
}
int a;
};
class B : public A
{
public:
B( int _a, int _b ) : A( _a ), b( _b ) {}
virtual bool operator==( const B& right ) const
{
return A::operator==( right ) && b == right.b;
}
int b;
};
如您所见,运算符 != 定义在基 class 中。因为我很懒,不想在所有派生的classes.
中重复这么简单的代码
不幸的是,代码如下:
A a4(4), a5(5), a4bis(4);
assert( a4 == a4bis );
assert( a4 != a5 );
B b1(4,5), b2(4,6);
assert( !(b1 == b2) );
assert( b1 != b2 ); // fails because B::operator== is not called!
b1 != b2
returns false,因为它执行 A::operator!=
然后调用 A::operator==
而不是 B::operator==
(即使运算符是虚拟的,因为派生class 版本参数不同,它们在 vtable 中没有链接)。
那么以通用方式为 class 层次结构寻址 != 运算符的最佳方式是什么?
一个解决方案是在每个 class 中重复它,然后 B
将有:
virtual bool operator!=( const B& right ) const
{
return !( *this == right );
}
但是当你有很多 classes 时,这很痛苦....我有 30....
另一种解决方案是采用通用模板方法:
template <class T>
bool operator!=( const T& left, const T& right )
{
return !( left == right );
}
但这会绕过任何 class 定义的任何 !=
运算符...因此,如果有人以不同的方式声明它(或者如果有人声明 ==
本身调用,则可能会有风险!=
,它会以无限循环结束......)。所以我觉得这个解决方案非常不安全....除非我们可以限制模板用于从我们的层次结构的顶层 class 派生的所有 classes (A
在我的示例中)...但我认为这根本不可行。
注意:我还没有使用 C++11...对此感到抱歉。
你的函数在B
...
virtual bool operator==( const B& right ) const
...不会覆盖A
中的函数...
virtual bool operator==( const A& right ) const
...因为参数类型不同。 (差异仅允许用于协变 return 类型。)
如果您更正此问题,您将能够选择如何将 B
对象与其他 A
和 A
派生的对象进行比较,例如也许:
bool operator==( const A& right ) const override
{
if (A::operator==( right ))
if (typeid(*this) == typeid(right))
return b == static_cast<const B&>(right).b;
return false;
}
请注意,使用上面的 typeid
比较意味着只有 B
对象将比较相等:任何 B
将与任何 B
派生对象比较不相等。这可能是也可能不是您想要的。
有了 B::operator==
的实现,现有的 !=
实现将正确包装 operator==
。正如 Jarod42 所观察到的,您的 A::operator==
并不健壮,因为当左侧值是 A
时,只会比较右侧对象的 A
切片...您可能更喜欢:
virtual bool operator==(const A& right) const
{
return a == right.a && typeid(*this) == typeid(right);
}
这与上面的 B::operator==
存在相同的问题:例如A
将不等于未引入更多数据成员的派生对象。
这样的事情怎么样?
class A {
protected :
virtual bool equals(const A& right) const {
return (a == right.a);
}
public :
A(int _a) : a(_a) { }
bool operator==(const A& right) const {
return this->equals(right);
}
bool operator!=(const A& right) const {
return !(this->equals(right));
}
int a;
};
class B : public A {
protected :
virtual bool equals(const A& right) const {
if (const B* bp = dynamic_cast<const B*>(&right)) {
return A::equals(right) && (b == bp->b);
}
return false;
}
public :
B(int _a, int _b) : A(_a), b(_b) { }
int b;
};
将比较逻辑移动到单独的(虚拟)函数 equals
,并从基 class 中定义的 operator==
和 operator!=
调用该函数。
不需要在派生的 classes 中重新定义运算符。要更改派生 class 中的比较,只需覆盖 equals
.
请注意,上面代码中的dynamic_cast
用于确保运行时类型是执行比较的有效类型。 IE。对于 B::equals
,它用于确保 right
是 B
- 这是必要的,否则 right
将不会有 b
成员。
这是一个非常简单的 class 层次结构:
class A
{
public:
A( int _a ) : a( _a ) {}
virtual bool operator==( const A& right ) const
{
return a == right.a;
}
virtual bool operator!=( const A& right ) const
{
return !( *this == right );
}
int a;
};
class B : public A
{
public:
B( int _a, int _b ) : A( _a ), b( _b ) {}
virtual bool operator==( const B& right ) const
{
return A::operator==( right ) && b == right.b;
}
int b;
};
如您所见,运算符 != 定义在基 class 中。因为我很懒,不想在所有派生的classes.
中重复这么简单的代码不幸的是,代码如下:
A a4(4), a5(5), a4bis(4);
assert( a4 == a4bis );
assert( a4 != a5 );
B b1(4,5), b2(4,6);
assert( !(b1 == b2) );
assert( b1 != b2 ); // fails because B::operator== is not called!
b1 != b2
returns false,因为它执行 A::operator!=
然后调用 A::operator==
而不是 B::operator==
(即使运算符是虚拟的,因为派生class 版本参数不同,它们在 vtable 中没有链接)。
那么以通用方式为 class 层次结构寻址 != 运算符的最佳方式是什么?
一个解决方案是在每个 class 中重复它,然后 B
将有:
virtual bool operator!=( const B& right ) const
{
return !( *this == right );
}
但是当你有很多 classes 时,这很痛苦....我有 30....
另一种解决方案是采用通用模板方法:
template <class T>
bool operator!=( const T& left, const T& right )
{
return !( left == right );
}
但这会绕过任何 class 定义的任何 !=
运算符...因此,如果有人以不同的方式声明它(或者如果有人声明 ==
本身调用,则可能会有风险!=
,它会以无限循环结束......)。所以我觉得这个解决方案非常不安全....除非我们可以限制模板用于从我们的层次结构的顶层 class 派生的所有 classes (A
在我的示例中)...但我认为这根本不可行。
注意:我还没有使用 C++11...对此感到抱歉。
你的函数在B
...
virtual bool operator==( const B& right ) const
...不会覆盖A
中的函数...
virtual bool operator==( const A& right ) const
...因为参数类型不同。 (差异仅允许用于协变 return 类型。)
如果您更正此问题,您将能够选择如何将 B
对象与其他 A
和 A
派生的对象进行比较,例如也许:
bool operator==( const A& right ) const override
{
if (A::operator==( right ))
if (typeid(*this) == typeid(right))
return b == static_cast<const B&>(right).b;
return false;
}
请注意,使用上面的 typeid
比较意味着只有 B
对象将比较相等:任何 B
将与任何 B
派生对象比较不相等。这可能是也可能不是您想要的。
有了 B::operator==
的实现,现有的 !=
实现将正确包装 operator==
。正如 Jarod42 所观察到的,您的 A::operator==
并不健壮,因为当左侧值是 A
时,只会比较右侧对象的 A
切片...您可能更喜欢:
virtual bool operator==(const A& right) const
{
return a == right.a && typeid(*this) == typeid(right);
}
这与上面的 B::operator==
存在相同的问题:例如A
将不等于未引入更多数据成员的派生对象。
这样的事情怎么样?
class A {
protected :
virtual bool equals(const A& right) const {
return (a == right.a);
}
public :
A(int _a) : a(_a) { }
bool operator==(const A& right) const {
return this->equals(right);
}
bool operator!=(const A& right) const {
return !(this->equals(right));
}
int a;
};
class B : public A {
protected :
virtual bool equals(const A& right) const {
if (const B* bp = dynamic_cast<const B*>(&right)) {
return A::equals(right) && (b == bp->b);
}
return false;
}
public :
B(int _a, int _b) : A(_a), b(_b) { }
int b;
};
将比较逻辑移动到单独的(虚拟)函数 equals
,并从基 class 中定义的 operator==
和 operator!=
调用该函数。
不需要在派生的 classes 中重新定义运算符。要更改派生 class 中的比较,只需覆盖 equals
.
请注意,上面代码中的dynamic_cast
用于确保运行时类型是执行比较的有效类型。 IE。对于 B::equals
,它用于确保 right
是 B
- 这是必要的,否则 right
将不会有 b
成员。