【问题标题】:Class Polymorphism and equality operators类多态和相等运算符
【发布时间】:2015-06-05 11:10:48
【问题描述】:

我正在努力解决我一直想知道的事情。 假设我有一堂课Base

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

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

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

还有一个类也继承自 Base,它也有两个相等运算符:

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

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

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

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

我一直在使用并且非常丑陋的东西,正在尝试向下转换,如果可以,请尝试在 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;
}

这很丑陋,它假设您的 A 类和 B 类有一个相等运算符,该运算符将相同类型的类作为参数。

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

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

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

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

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

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

【问题讨论】:

  • “但是当我尝试运行它时,它只是挂起” - 发布代码。
  • @vsoftco 所以,你的意思是只有 Base 是可比较的,而不是任何派生类?
  • @Alex,一点也不,operator== 是继承的,如果 getter 是 virtual,那么你就可以走了。
  • @Alex,我的意思是层次结构应该通过公共Base 接口进行比较,否则设计不是最好的(恕我直言)。当您谈论继承时,要记住的习惯用法是Derived总是也是Base。所以原则上Rectangle 不是Square,即使你第一次想把它写成派生类。

标签: c++ inheritance polymorphism equality


【解决方案1】:

我同意@vsoftco 关于只在基类中实现operator== 并使用NVI 成语的观点。但是,我会提供一个纯虚函数,派生类需要实现它来执行相等性检查。这样,基类就不知道也不关心任何派生类等效意味着什么。

代码

#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

【讨论】:

  • 这实际上是我一开始所希望的。很聪明,我看看能不能用,非常感谢。
  • 非常感谢,这是迄今为止我见过的最优雅的东西,赞!我不确定我是否理解前提条件的工作原理,但我会用谷歌搜索!
  • @Alex 前提条件只是记录为使事情正常工作必须遵守的要求。调用者有责任(即Base::operator==)确保满足前提条件。这类似于std::vector&lt;T&gt;::operator[](index) 的前提条件,前提条件是index 没有越界,否则存在未定义的行为
【解决方案2】:

一般来说,在一个层次结构中,应该有一个公共接口,并且 operator== 应该只在 Base 类中使用来自接口的(虚拟)getter 实现。否则就像在层次结构中重新定义函数(不使用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,是所谓template method(与模板无关,只是一个不幸的名称)的一种表现形式,其中您有客户端(沿着层次结构)间接调用虚函数通过公共的非虚拟成员函数。 Scott Meyers 的 Effective C++ 的第 55 条对此问题进行了精彩的讨论。

【讨论】:

  • 非常感谢 vsoftcol,我会看看如何改进它,因为我对当前的代码不满意!
  • @Alex 没问题,希望对您有所帮助。
  • @Alex 我在很多方面都同意 vsoftco。但是,我提供了一个略有不同的解决方案,它不涉及虚拟吸气剂。相反,它使用 NVI 习语并将比较的逻辑封装在派生类中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-24
  • 2012-02-21
  • 1970-01-01
相关资源
最近更新 更多