C++ 什么';对于类层次结构,重载运算符==的正确方法是什么?

C++ 什么';对于类层次结构,重载运算符==的正确方法是什么?,c++,operator-overloading,C++,Operator Overloading,假设我有以下类层次结构: class A { int foo; virtual ~A() = 0; }; A::~A() {} class B : public A { int bar; }; class C : public A { int baz; }; 对于这些类,重载操作符==的正确方法是什么?如果我让它们都是免费函数,那么B和C就不能不强制转换就利用A的版本。它还可以防止某些人只引用a进行深入比较。如果我将它们设置为虚拟成员函数,则派生版本可能如

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

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

A::~A() {}

class B : public A
{
    int bar;
};

class C : public A
{
    int baz;
};
对于这些类,重载
操作符==
的正确方法是什么?如果我让它们都是免费函数,那么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;
    }
}
bool B::operator==(常量A和rhs)常量
{
常数B*ptr=动态施法(&rhs);
如果(ptr!=0){
返回(bar==ptr->bar)&&(A::operator==(*this,rhs));
}
否则{
返回false;
}
}
再说一次,我还是要投(感觉不对)。有没有更好的方法

更新:

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

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

任何试图比较两个不同类型对象的用户都不会编译,因为基函数受保护,叶类可以利用父版本来比较数据的一部分。

< P>对于这种层次结构,我肯定会遵循Scott Meyer的有效C++建议,避免使用任何具体的基类。无论如何,你似乎都在这样做

我将把
operator==
实现为一个免费函数,可能是friends,仅用于具体的叶节点类类型

如果基类必须有数据成员,那么我将在基类(
isEqual
)中提供一个(可能是受保护的)非虚拟助手函数,派生类的
操作符==
可以使用该函数

例如

通过避免使用在抽象基类上工作的
运算符==
,并保护比较函数,您永远不会在客户端代码中意外地出现回退,在客户端代码中,只比较两个不同类型对象的基部分

我不确定我是否会实现一个带有
动态转换的虚拟比较函数,我不愿意这样做,但如果确实需要它,我可能会在基类中使用一个纯虚拟函数(不是
操作符==
),然后在具体的派生类中重写它,就像这样,对派生类使用
运算符==

bool B::pubIsEqual( const A& rhs ) const
{
    const B* b = dynamic_cast< const B* >( &rhs );
    return b != NULL && *this == *b;
}
bool B::publisequal(const A&rhs)const
{
常数B*B=动态施法<常数B*>(&rhs);
返回b!=NULL&&*this===b;
}

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

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;
    }
};
结构A { int foo; A(int-prop):foo(prop){} 虚拟~A(){} 虚拟布尔运算符==(常量A和其他)常量 { 如果(类型ID(*此)!=类型ID(其他)) 返回false; 返回foo==other.foo; } }; 结构B:A { int-bar; B(int-prop):A(1),bar(prop){} 布尔运算符==(常数A和其他)常数 { 如果(!A::operator==(其他)) 返回false; 返回条==静态_转换(其他).bar; } }; 结构C:A { int baz; C(int-prop):A(1),baz(prop){} 布尔运算符==(常数A和其他)常数 { 如果(!A::operator==(其他)) 返回false; return baz==static_cast(其他).baz; } };

我不喜欢的是typeid检查。您对此有何看法?

如果您合理地假设两个对象的类型必须相同才能相等,那么有一种方法可以减少每个派生类中所需的锅炉板数量。这样做是为了保护虚拟方法并将其隐藏在公共接口后面。用于实现
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;
};
A类
{
公众:
布尔运算符==(常数A&A)常数
{
回报等于(a);
}
受保护的:
虚拟布尔等于(常数A&A)常数=0;
};
模板
A类:公共A
{
受保护的:
虚拟布尔等于(常数A&A)常数
{
const T*other=动态施法(&a);
返回其他!=nullptr&&static_cast(*this)==*other;
}
私人:
bool操作符==(const A_&A)const//强制派生类实现它们自己的操作符==
{
返回false;
}
};
B类:公共A_
{
公众:
B(inti):id(i){}
布尔运算符==(常量B和其他)常量
{
返回id==other.id;
}
私人:
int-id;
};
C类:公共A类_
{
公众:
C(inti):恒等式(i){}
布尔运算符==(常量C和其他)常量
{
返回标识==other.identity;
}
私人:
智力同一性;
};
在上查看演示

  • 我觉得这看起来很奇怪:

    void foo(const MyClass& lhs, const MyClass& rhs) {
      if (lhs == rhs) {
        MyClass tmp = rhs;
        // is tmp == rhs true?
      }
    }
    
  • >p>如果实现运算符==看起来是一个合法的问题,考虑类型擦除(考虑类型擦除无论如何,这是一个可爱的技术)。 然后,您仍然需要执行多次调度。这是一个令人不快的问题

  • 考虑使用变体而不是层次结构。他们可以轻松地做这类事情


  • 如果你不想使用Cube,并且确保你不会意外地比较B的实例和C的实例,那么你需要用Scott Meyers在更有效的C++ 33中提出的方式来重构类层次结构。实际上,该项处理赋值运算符,如果用于非相关类型,则它实际上毫无意义。在比较操作的情况下,在比较B和C的实例时返回false是有意义的

    下面是使用RTTI的示例代码,它并没有将类层次结构划分为concreate leaf和abstract base

    这个示例代码的好处是,在比较不相关的实例(如B和C)时,不会得到std::bad_cast。尽管如此,编译器仍将
    void foo(const MyClass& lhs, const MyClass& rhs) {
      if (lhs == rhs) {
        MyClass tmp = rhs;
        // is tmp == rhs true?
      }
    }
    
    #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));
    }