Class 多态性和相等运算符

Class Polymorphism and equality operators

我正在努力解决我已经想了很久的事情。 假设我有一个 class Base

class Base
{
public:
    virtual ~Base(){}
    virtual bool operator== ( const Base & rhs ) const;
};

现在,另一个 class 继承了它。它有两个相等运算符:

class A : public Base
{
public:
    bool operator== ( const A & rhs ) const;
    bool operator== ( const Base & rhs ) const;
private:
    int index__;
};

还有一个 class 也继承自 Base,并且也有两个相等运算符:

class B : public Base
{
public:
    bool operator== ( const B & rhs ) const;
    bool operator== ( const Base & rhs ) const;
private:
    int index__;
};

这是我的理解(不一定正确)。 我只能使用第一个运算符来检查相同的 class 对象是否相等。 然而,我可以使用第二个运算符来检查它们是否是同一类型的 class,然后检查它们是否相等。 现在,还有另一个 class 存在,它环绕着 Base 的指针,然而,它们是多态类型 A 或 B。

class Z
{
public:
    bool operator== ( const Z & rhs ) const;
private:
    std::shared_ptr<Base> ptr__;
};

首先,我发现我不能让两个 operator== 重载。我没有从编译器中得到任何错误,但是当我尝试 运行 它时,它只是挂起。我猜想和rtti有关系,这超出了我的范围。

我一直在使用的是非常丑陋的东西,正在尝试向下转换,如果可以的话,然后尝试比较实例,在 class Z:

bool Z::operator== ( const Z & rhs ) const
{
    if ( const auto a1 = std::dynamic_pointer_cast<A>( this->ptr__ ) )
        if ( const auto a2 = std::dynamic_pointer_cast<A>( rhs.ptr__ ) )
            return *a1 == *a2; 
    else if ( const auto b1 = std::dynamic_pointer_cast<B>( this->ptr__ ) )
        if ( const auto b2 = std::dynamic_pointer_cast<B>( rhs.ptr__ ) )
            return *b1 == *b2;
    return false;
}

这很丑陋,它假设你的 class A 和 B 有一个相等运算符,它接受相同类型的参数 class。

所以我试图想出一种方法,它会使用第二种类型的运算符,如果你愿意的话,它更不可知,更优雅。失败了。这将需要在 classes A 和 B 中使用它,从而将它从 class Z 移开。

bool A::operator== ( const Base & rhs ) const
{
    return ( typeid( *this ) == typeid( rhs ) ) && ( *this == rhs );
}

与 class B 相同。这似乎不起作用(应用挂起且没有任何错误)。 此外,它使用某种默认运算符,还是使用基本 class 运算符?理想情况下,它应该同时使用 Base::operator== 和比较 class 类型。

但是,如果我想要更详细的比较,基于 class A 或 B 的成员,例如 index__,那么我显然必须将每个 class 加为好友,因为当我尝试这个时,它不会编译(当然除非我添加 getter 或使其以某种方式可见):

bool A::operator== ( const Base & rhs ) const
{
    return ( typeid( *this ) == typeid( rhs ) )
           && (*this == *rhs )
           && (this->index__ == rhs.index__ );
}

有没有优雅、简单的解决方案?我是局限于沮丧和尝试,还是有其他方法可以实现我想要的?

一般来说,在一个层次结构中,应该有一个公共接口,imo operator== 应该只在 Base class 中使用来自界面。否则就像在层次结构中重新定义函数(不使用 virtual)一样,这几乎总是一个坏主意。所以你可能想考虑一下你的设计,有多个 operator== 似乎很可疑。

非常简单的例子:

#include <iostream>

class A
{
    int _x;
public:
    A(int x):_x(x){}
    virtual int getx() const { return _x; } // runtime
    bool operator==(const A& other){return getx() == other.getx();} // one implementation
};

class B: public A
{
    using A::A;
    int getx() const override // Make all B's equal, runtime
    {
        return 0; // so always 0, all B's are equal
    }
};

int main()
{
    A a1(10), a2(20);
    B b1(10), b2(20);
    std::cout << std::boolalpha << (a1==a2) << std::endl; // false
    std::cout << std::boolalpha << (b1==b2) << std::endl; // always true
}

这个模式通常被称为 the non-virtual interface idiom and is a manifestation of the so called template method (has nothing to do with templates, just an unfortunate name), in which you have clients (along the hierarchy) call virtual functions indirectly through public non-virtual member functions. Item 55 of Scott Meyers' Effective C++ 对这个问题有很好的讨论。

我同意@vsoftco 关于仅在基础 class 中实现 operator== 并使用 NVI 习惯用法。但是,我会提供派生的 classes 需要实现的纯虚函数来执行相等性检查。通过这种方式,基础 class 不知道或不关心任何派生的 class 是什么意思 等效 .

代码

#include <iostream>
#include <string>
#include <typeinfo>

class Base
{
public:
    virtual ~Base() {}

    bool operator==(const Base& other) const
    {
        // If the derived types are the same then compare them
        return typeid(*this) == typeid(other) && isEqual(other);
    }

private:
    // A pure virtual function derived classes must implement.
    // Furthermore, this function has a precondition that it will only
    // be called when the 'other' is the same type as the instance
    // invoking the function.
    virtual bool isEqual(const Base& other) const = 0;
};

class D1 : public Base
{
public:
    explicit D1(double v = 0.0) : mValue(v) {}
    virtual ~D1() override {}

private:
    virtual bool isEqual(const Base& other) const
    {
        // The cast is safe because of the precondition documented in the
        // base class
        return mValue == static_cast<const D1&>(other).mValue;
    }

private:
    double mValue;
};

class D2 : public Base
{
public:
    explicit D2(std::string v = "") : mValue(v) {}
    virtual ~D2() override {}

private:
    virtual bool isEqual(const Base& other) const
    {
        return mValue == static_cast<const D2&>(other).mValue;
    }

private:
    std::string mValue;
};

class D3 : public Base
{
public:
    explicit D3(int v = 0) : mValue(v) {}
    virtual ~D3() override {}

private:
    virtual bool isEqual(const Base& other) const
    {
        return mValue == static_cast<const D3&>(other).mValue;
    }

private:
    int mValue;
};

int main()
{
    D1 d1a(1.0);
    D1 d1b(2.0);
    D1 d1c(1.0);

    D2 d2a("1");
    D2 d2b("2");
    D2 d2c("1");

    D3 d3a(1);
    D3 d3b(2);
    D3 d3c(1);

    std::cout << "Compare D1 types\n";
    std::cout << std::boolalpha << (d1a == d1b) << "\n";
    std::cout << std::boolalpha << (d1b == d1c) << "\n";
    std::cout << std::boolalpha << (d1a == d1c) << "\n";

    std::cout << "Compare D2 types\n";
    std::cout << std::boolalpha << (d2a == d2b) << "\n";
    std::cout << std::boolalpha << (d2b == d2c) << "\n";
    std::cout << std::boolalpha << (d2a == d2c) << "\n";

    std::cout << "Compare D3 types\n";
    std::cout << std::boolalpha << (d3a == d3b) << "\n";
    std::cout << std::boolalpha << (d3b == d3c) << "\n";
    std::cout << std::boolalpha << (d3a == d3c) << "\n";

    std::cout << "Compare mixed derived types\n";
    std::cout << std::boolalpha << (d1a == d2a) << "\n";
    std::cout << std::boolalpha << (d2a == d3a) << "\n";
    std::cout << std::boolalpha << (d1a == d3a) << "\n";
    std::cout << std::boolalpha << (d1b == d2b) << "\n";
    std::cout << std::boolalpha << (d2b == d3b) << "\n";
    std::cout << std::boolalpha << (d1b == d3b) << "\n";
    std::cout << std::boolalpha << (d1c == d2c) << "\n";
    std::cout << std::boolalpha << (d2c == d3c) << "\n";
    std::cout << std::boolalpha << (d1c == d3c) << "\n";

    return 0;
}

输出

Compare D1 types
false
false
true
Compare D2 types
false
false
true
Compare D3 types
false
false
true
Compare mixed derived types
false
false
false
false
false
false
false
false
false