问题 C ++中派生类的等式测试[重复]


可能重复:
什么是对类层次结构重载operator ==的正确方法? 

在C ++中,派生类如何以有意义的方式覆盖基类相等性测试?

例如,假设我有一个基类A.类B和C派生自A.现在给出两个指向两个A对象的指针,我可以测试它们是否相等(包括任何子类数据)吗?

class A {
    public: int data;
};

class B : public A {
    public: float more_data; bool something_else;
};

class C : public A {
    public: double more_data;
};


    A* one = new B;
    A* two = new B;
    A* three = new C;

    //How can I test if one, two, or three are equal
    //including any derived class data?

这样做有干净的方法吗?什么是我最好的选择?

谢谢!


10403
2017-11-19 17:36


起源

可能重复: stackoverflow.com/questions/1691007/... - CB Bailey
你想比较吗? T==T,哪里 T 也许是 A, B, 要么 C (好),或者你想比较 A 同 B 和 A 同 C 和 B 同 C (可疑的)? - sbi
在上面的例子中,我想比较一个与两个和三个。他们都是A指针。 - Imbue
@Imbue:然后你想要的东西毫无意义。既然只有一个 B 具有 something_else  它永远不会是平等的 到了 A 或者a C。这种比较的语义是什么? - sbi
@sbi:我不是要求将B与A或B与C进行比较。我要求将两个As进行比较。在我上面的例子中,一个,两个和三个都是A *。 - Imbue


答案:


我记得读过公共 - 非虚拟/非公共 - 虚拟习语及其优点的简洁描述,但不是在哪里。 这个wikibook 有一个好的描述。

以下是将其应用于op ==的方法:

struct A {
  virtual ~A() {}

  int a;

  friend
  bool operator==(A const& lhs, A const& rhs) {
    return lhs.equal_to(rhs);
  }
  // http://en.wikipedia.org/wiki/Barton-Nackman_trick
  // used in a simplified form here

protected:
  virtual bool equal_to(A const& other) const {
    return a == other.a;
  }
};

struct B : A {
  int b;

protected:
  virtual bool equal_to(A const& other) const {
    if (B const* p = dynamic_cast<B const*>(&other)) {
      return A::equal_to(other) && b == p->b;
    }
    else {
      return false;
    }
  }
};

struct C : A {
  int c;

protected:
  virtual bool equal_to(A const& other) const {
    if (C const* p = dynamic_cast<C const*>(&other)) {
      return A::equal_to(other) && c == p->c;
    }
    else {
      return false;
    }
  }
};

13
2017-11-19 17:45





不同的派生类可以制作相同的对象吗

如果是这样: 双重调度 是一个选项:它确实需要在基类中重载,因此您将拥有依赖项

如果不是:运算符==()中的解决方案检查typeid,如果它们不同则返回false。否则调用一个私有的equal()函数,在该函数中派生类可以执行static_cast并进行比较。

bool base::operator==(const base& other) const
{
  if (typeid(*this) != typeid(other)) return false;
  return equal(other);
}

bool derived::equal(const base& other) const
{
  derived& derOther = static_cast<derived&>(other);
  // compare derOther with *this
  return true;  // there is nothing to compare
}

这避免了所有派生类中的类型检查


2
2017-11-19 17:54



这可以防止使用B的派生类(比方说B2)与B进行比较。(使用问题中的层次结构。)
operator ==()仅在基类中定义,因此可以使用层次结构。 equal()函数必须是私有的(如上所述),并且只能由operator ==()调用 - stefaanv


一种方法是使用 virtual operator== 它将基类对象作为参数,以便它可以与不同的派生对象一起正常工作。但是,您需要将此函数设置为纯虚拟,以强制所有派生对象实现它。所以你将无法实例化基类。例如:

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

    //A virtual operator for comparison
    virtual bool operator==(const A& a) = 0;

protected:
    bool compareBase(const A& a);

private:
    int m_data;
};

bool A::compareBase(const A &a)
{
    return m_data == a.m_data;
}

class B1 : public A
{
public:

    //Override the base class
    bool operator==(const A& a);

private:
    bool compare(const B1* pB)
    {
        if(compareBase(*pB))
        {
            //Code for compare
            return true;
        }

        return false;
    }
};

bool B1::operator ==(const A &a)
{
    //Make sure that the passed type is same
    const B1* pB = dynamic_cast<const B1*>(&a);
    if(pB )
    {
        return compare(pB);
    }

    return false;
}
//Similarly implement for B2

1
2017-11-19 17:53



在基类中使用非纯公共虚拟运算符==可能非常危险,因为对于忘记覆盖它的新派生类没有保护或警告,并且如果基本部分相等则最终比较相等。 - CB Bailey
你是对的。编辑代码以使其成为纯虚拟 - Naveen
虽然我同意 A 应该是抽象的,我认为它根本不需要(或应该有)和operator ==。因为它表达如 a == b 将根据顺序和类型有很大不同的bevahiour a 和 b 而他们可能期望是对称的。 operator== 只对具有值语义的类型才有意义。 - CB Bailey


如果你不关心类型A与类型B或B到C等的比较,那么你可以简单地为每个类实现一个重载的相等运算符:

class A {
    public: int data;

    bool operator==(const A& rhs) {
        return (data == rhs.data);
    }
};
class B : public A {
    public: float more_data; bool something_else;

    bool operator==(const B& rhs) {
        return (A::operator==( data ) &&
                more_data == rhs.more_data &&
                something_else == rhs.something_else);
    }
};

这很危险,因为如果你从B或C派出一个新的D类,你就会遇到问题。

否则你需要实现一些具有大量dynamic_cast <>的比较器才能真正做到正确。或者,您可以实现一个函数来为每个对象创建一个哈希代码并利用它,例如,

class A {
    public: int data;

    virtual long getHashCode() const {
        // compute something here for object type A
    }

    // virtual here just in case you need to overload it in B or C
    virtual bool equals( const A& obj ) const {
        return (typeid(*this) == typeid(obj) &&
                getHashCode() == obj->getHashCode());
    }
};

class B : public A {
    public: float more_data; bool something_else;

    virtual long getHashCode() const {
        // compute something here for object type B
    }
};

class C : public A {
    public: double more_data;

    virtual long getHashCode() const {
        // compute something here for object type C
    }
};

如果您以某种方式将对象的类型合并到哈希代码中(上面未显示),那么您也可以省去上面的愚蠢的typeid()比较。


0
2017-11-19 18:02





如果你不介意引用子类的基类,那么双重调度:

#include <iostream>

class B;
class C;

class A
{
public:
    int data;

    virtual bool equals (const A* rhs) const
    {
        std::cout << " A==A ";
        return data == rhs->data;
    }

    virtual bool equals (const B* rhs) const {return false;}
    virtual bool equals (const C* rhs) const {return false;}
};

class B : public A
{
public:
    float some_data;

    virtual bool equals (const A* rhs) const
    {
        return rhs->equals (this);
    }

    virtual bool equals (const B* rhs) const
    {
        std::cout << " B==B ";
        return A::equals (static_cast<const A*> (rhs)) && some_data == rhs->some_data;
    }
};

class C : public A
{
public:
    double more_data;

    virtual bool equals (const A* rhs) const
    {
        return rhs->equals (this);
    }

    virtual bool equals (const C* rhs) const
    {
        std::cout << " C==C ";
        return A::equals (static_cast<const A*> (rhs)) && more_data == rhs->more_data;
    }
};

bool operator== (const A& lhs, const A& rhs)
{
    return lhs.equals (&rhs);
}

int main (int argc, char* argv[])
{

    A* one = new B;
    A* two = new B;
    A* three = new C;

    std::cout << (*one == *one) << std::endl;
    std::cout << (*one == *two) << std::endl;
    std::cout << (*one == *three) << std::endl;
    std::cout << (*three == *three) << std::endl;

    return 0;
}

它是否不需要dynamic_casts。


0
2017-11-19 18:11