为类层次结构重载operator ==的正确方法是什么?

Mic*_*fik 48 c++ operator-overloading

假设我有以下类层次结构:

class A
{
    int foo;
    virtual ~A() = 0;
};

A::~A() {}

class B : public A
{
    int bar;
};

class C : public A
{
    int baz;
};
Run Code Online (Sandbox Code Playgroud)

operator==为这些类重载的正确方法是什么?如果我让它们都是自由函数,那么B和C不能在没有强制转换的情况下利用A的版本.它还会阻止某人进行只有A引用的深度比较.如果我将它们作为虚拟成员函数,那么派生版本可能如下所示:

bool B::operator==(const A& rhs) const
{
    const B* ptr = dynamic_cast<const B*>(&rhs);        
    if (ptr != 0) {
        return (bar == ptr->bar) && (A::operator==(*this, rhs));
    }
    else {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

再次,我仍然需要施放(并且感觉不对).有没有一种首选的方法呢?

更新:

到目前为止只有两个答案,但看起来正确的方式类似于赋值运算符:

  • 使非叶类抽象化
  • 在非叶类中受保护的非虚拟
  • 在叶类中公共非虚拟

任何用户尝试比较两个不同类型的对象都不会编译,因为基本功能受到保护,叶子类可以利用父类的版本来比较那部分数据.

CB *_*ley 20

对于这种层次结构,我肯定会遵循Scott Meyer的Effective C++建议,避免使用任何具体的基类.在任何情况下,您似乎都是这样做的.

我将实现operator==作为一个自由函数,可能是朋友,仅用于具体的叶节点类类型.

如果基类必须有数据成员,那么我将在基类(isEqual比方说)中提供(可能受保护的)非虚拟辅助函数,派生类operator==可以使用它.

例如

bool operator==(const B& lhs, const B& rhs)
{
    lhs.isEqual( rhs ) && lhs.bar == rhs.bar;
}
Run Code Online (Sandbox Code Playgroud)

通过避免operator==在抽象基类上工作并保持比较函数受到保护,您不会在客户端代码中意外地回退,其中只比较两个不同类型对象的基本部分.

我不确定我是否用a实现了虚拟比较函数dynamic_cast,我不愿意这样做但是如果已经证明需要它我可能会在基类中使用纯虚函数(不是 operator==)然后在具体派生类中重写为类似的东西,使用operator==for派生类.

bool B::pubIsEqual( const A& rhs ) const
{
    const B* b = dynamic_cast< const B* >( &rhs );
    return b != NULL && *this == *b;
}
Run Code Online (Sandbox Code Playgroud)

  • 你明确需要抽象类中的operator ==才能授予多态性.我不认为这个答案是好的,因为它没有解决问题. (5认同)

Job*_*Job 12

前几天我遇到了同样的问题,我提出了以下解决方案:

struct A
{
    int foo;
    A(int prop) : foo(prop) {}
    virtual ~A() {}
    virtual bool operator==(const A& other) const
    {
        if (typeid(*this) != typeid(other))
            return false;

        return foo == other.foo;
    }
};

struct B : A
{
    int bar;
    B(int prop) : A(1), bar(prop) {}
    bool operator==(const A& other) const
    {
        if (!A::operator==(other))
            return false;

        return bar == static_cast<const B&>(other).bar;
    }
};

struct C : A
{
    int baz;
    C(int prop) : A(1), baz(prop) {}
    bool operator==(const A& other) const
    {
        if (!A::operator==(other))
            return false;

        return baz == static_cast<const C&>(other).baz;
    }
};
Run Code Online (Sandbox Code Playgroud)

我不喜欢这件事是打字检查.你怎么看待这件事?


Mar*_*som 9

如果你合理地假设两个对象的类型必须相同才能使它们相等,那么就有办法减少每个派生类所需的样板量.这遵循Herb Sutter的建议,即保护虚拟方法并将其隐藏在公共接口之后.该奇异递归模板模式(CRTP)来实现的样板代码的equals方法,从而派生类不需要.

class A
{
public:
    bool operator==(const A& a) const
    {
        return equals(a);
    }
protected:
    virtual bool equals(const A& a) const = 0;
};

template<class T>
class A_ : public A
{
protected:
    virtual bool equals(const A& a) const
    {
        const T* other = dynamic_cast<const T*>(&a);
        return other != nullptr && static_cast<const T&>(*this) == *other;
    }
private:
    bool operator==(const A_& a) const  // force derived classes to implement their own operator==
    {
        return false;
    }
};

class B : public A_<B>
{
public:
    B(int i) : id(i) {}
    bool operator==(const B& other) const
    {
        return id == other.id;
    }
private:
    int id;
};

class C : public A_<C>
{
public:
    C(int i) : identity(i) {}
    bool operator==(const C& other) const
    {
        return identity == other.identity;
    }
private:
    int identity;
};
Run Code Online (Sandbox Code Playgroud)

请访问http://ideone.com/SymduV上的演示

  • 根据您的假设,我认为检查基类运算符中的 typeid 相等性并直接在 equals 函数中使用静态强制转换会更有效、更安全。使用dynamic_cast意味着如果T有另一个派生类,称之为X,则可以通过基类比较类型T和X的对象并发现它们相等,即使只有公共T部分实际上是等效的。也许在某些情况下这是您想要的,但在大多数其他情况下这将是一个错误。 (2认同)

mar*_*inj 8

如果您不想使用强制转换,并且还确保您不会偶然将B的实例与C的实例进行比较,那么您需要按照Scott Meyers在“更有效的C ++”第33项中建议的方式来重新构造类的层次结构。实际上,此项目与赋值运算符有关,如果将其用于不相关的类型,则实际上没有任何意义。在进行比较操作的情况下,在将B的实例与C的实例进行比较时,返回false是有意义的。

下面是使用RTTI的示例代码,没有将类层次结构划分为创建叶和抽象库。

此示例代码的好处是,在比较不相关的实例(例如B与C)时,您不会获得std :: bad_cast。尽管如此,编译器仍允许您执行所需的操作,您可以以相同的方式实现operator <并将其用于对各种A,B和C实例的向量进行排序。

生活

#include <iostream>
#include <string>
#include <typeinfo>
#include <vector>
#include <cassert>

class A {
    int val1;
public:
    A(int v) : val1(v) {}
protected:
    friend bool operator==(const A&, const A&);
    virtual bool isEqual(const A& obj) const { return obj.val1 == val1; }
};

bool operator==(const A& lhs, const A& rhs) {
    return typeid(lhs) == typeid(rhs) // Allow compare only instances of the same dynamic type
           && lhs.isEqual(rhs);       // If types are the same then do the comparision.
}

class B : public A {
    int val2;
public:
    B(int v) : A(v), val2(v) {}
    B(int v, int v2) : A(v2), val2(v) {}
protected:
    virtual bool isEqual(const A& obj) const override {
        auto v = dynamic_cast<const B&>(obj); // will never throw as isEqual is called only when
                                              // (typeid(lhs) == typeid(rhs)) is true.
        return A::isEqual(v) && v.val2 == val2;
    }
};

class C : public A {
    int val3;
public:
    C(int v) : A(v), val3(v) {}
protected:
    virtual bool isEqual(const A& obj) const override {
        auto v = dynamic_cast<const C&>(obj);
        return A::isEqual(v) && v.val3 == val3;
    }
};

int main()
{
    // Some examples for equality testing
    A* p1 = new B(10);
    A* p2 = new B(10);
    assert(*p1 == *p2);

    A* p3 = new B(10, 11);
    assert(!(*p1 == *p3));

    A* p4 = new B(11);
    assert(!(*p1 == *p4));

    A* p5 = new C(11);
    assert(!(*p4 == *p5));
}
Run Code Online (Sandbox Code Playgroud)

  • 您应该使用static_cast而不是dynamic_cast。正如您已经检查了typeid一样,这是安全且快捷的。 (3认同)