【问题标题】:Operator== in derived class never gets called派生类中的 Operator== 永远不会被调用
【发布时间】:2010-01-26 23:07:17
【问题描述】:

有人可以帮我摆脱痛苦吗?我试图弄清楚为什么派生 operator== 永远不会在循环中被调用。为了简化示例,这是我的 Base 和 Derived 类:

class Base { // ... snipped
  bool operator==( const Base& other ) const { return name_ == other.name_; }
};

class Derived : public Base { // ... snipped
  bool operator==( const Derived& other ) const { 
    return ( static_cast<const Base&>( *this ) ==
             static_cast<const Base&>( other ) ? age_ == other.age_ :
                                                 false );
};

现在当我像这样实例化和比较时......

Derived p1("Sarah", 42);
Derived p2("Sarah", 42);
bool z = ( p1 == p2 );

...一切都很好。在这里,来自 Derived 的 operator== 被调用,但是当我遍历一个列表时,比较指向 Base 对象的指针列表中的项目...

list<Base*> coll;

coll.push_back( new Base("fred") );
coll.push_back( new Derived("sarah", 42) );
// ... snipped

// Get two items from the list.
Base& obj1 = **itr;
Base& obj2 = **itr2;

cout << obj1.asString() << " " << ( ( obj1 == obj2 ) ? "==" : "!=" ) << " "
     << obj2.asString() << endl;

这里asString()(它是虚拟的,为简洁起见,这里没有显示)工作正常,但obj1 == obj2 总是 调用Base operator==,即使这两个对象是Derived .

我知道当我发现问题所在时我会踢自己,但如果有人能轻轻地让我失望,我将不胜感激。

【问题讨论】:

    标签: c++ operators polymorphism virtual equals-operator


    【解决方案1】:

    那是因为您没有将 operator== 设为虚拟,因此在运行时不会考虑实际类型。

    不幸的是,仅仅将 operator== 设为虚拟并不能解决您的问题。原因是当您通过将参数的类型从基更改为派生来更改函数签名时,您实际上是在创建一个新函数。听起来您想查看double-dispatch 来解决您的问题。

    【讨论】:

    • 我不相信你真的可以让操作员虚拟化。
    • @Samuel:我不知道,谢谢你今天教我一些东西!
    • Double-dispatch 只是根据 Wikipedia 链接的虚函数的另一个名称。更改函数签名并不是他的问题,期望 C++ 选择基于动态类型的覆盖是。
    • @Potatoswatter - 仔细阅读。这篇文章将双调度与单调度调用进行了对比。然后他们注意到单调度调用也称为虚函数。
    • @R:这篇文章写得很糟糕,但对我来说,第一段说 OO 语言中的单调度调用就像双调度调用一样工作。同意他需要确定 operator== 的所有参数的类型,但我不同意通过调度程序函数的意大利面条控制流是要走的路。
    【解决方案2】:

    有两种方法可以解决此问题。

    第一个解决方案。我建议在循环中添加一些额外的类型逻辑,这样您就知道什么时候有Base,什么时候有Derived。如果您真的只处理 Derived 对象,请使用

    list<Derived*> coll;
    

    否则在某处放置dynamic_cast

    第二种解决方案。 将相同类型的逻辑放入您的operator==。首先将其设为虚拟,因此左侧操作数的类型在运行时确定。然后手动检查右手操作数的类型。

    virtual bool operator==( const Base& other ) const {
      if ( ! Base::operator==( other ) ) return false;
      Derived *other_derived = dynamic_cast< Derived * >( &other );
      if ( ! other_derived ) return false;
      return age_ == other_derived->age_;
    }
    

    但考虑到不同类型的对象可能不会相等,可能你想要的是

    virtual bool operator==( const Base& other ) const {
      Derived *other_derived = dynamic_cast< Derived * >( &other );
      return other_derived
       && Base::operator==( other )
       && age_ == other_derived->age_;
    }
    

    【讨论】:

    • 这是有道理的。 “真正的”应用程序使用智能指针(不幸的是不是 Boost,而是我正在使用的客户端的“自制”)进行比较,这使事情变得更加复杂。不过我会试试这个。值得庆幸的是,尽管该集合使用基本智能指针,但由于上下文,我可以合理地确定对象都将是通用(派生)类型。非常感谢。
    • 有趣的是,当我尝试这个时,我发现基类 operator== 的链导致 - 因为它是虚拟的 - 对派生 operator== 的调用(即无限递归)嗯.. .
    • 没问题。我已经开始添加一个受保护的静态 isEqual(lhs,rhs),它构成了我想做的事情的基础,并且可以从 op== 链接和调用 - 虽然有点笨拙:(
    【解决方案3】:

    您需要使operator== virtual 并且您需要确保它们两个方法具有相同的签名。即他们可能都需要接受Base。您可以在派生类中有一个重载的operator==,它能够处理派生对象。

    【讨论】:

      【解决方案4】:

      当成员函数是虚函数时,虚表在运行时用于多态调用指针实际指向的类型(在本例中为您的派生类)上的函数。当函数不是虚函数时,不会进行虚表查找,而是调用给定类型的函数(在这种情况下,是您的类 Base)。

      在这里,您的 operator=() 函数不是虚拟的,因此使用的是指针的类型,而不是指针指向的类型。

      【讨论】:

        【解决方案5】:

        对于派生类使用它们自己的运算符实现,该运算符在基类中必须是虚拟的,否则将使用基类实现。

        【讨论】:

        • 问题是,即使我将 operator== virtual 设置为虚拟,它也没有任何效果,因为签名不同。我认为,线索是让 Derived 类实现采用 Base 引用并在内部进行强制转换,因为在上下文中它应该始终被调用,而 other 是 Derived。
        猜你喜欢
        • 2012-10-27
        • 2013-10-12
        • 2012-03-27
        • 2013-08-29
        • 2013-11-03
        • 1970-01-01
        • 1970-01-01
        • 2015-05-06
        • 2015-10-15
        相关资源
        最近更新 更多