抽象类和运算符!=在c ++中

时间:2011-01-12 14:34:20

标签: c++

我在实现运算符时遇到问题!=在一个派生类中派生出一个抽象的类。代码如下所示:

class Abstract
{
  public:
     //to make the syntax easier let's use a raw pointer
     virtual bool operator!=(const Abstract* other) = 0;
};

class Implementation
{
    SomeObject impl_; //that already implement the operator!=
  public:
    bool operator!=(const Abstract* other)
    {
      return dynamic_cast<Implementation*>(other)->impl_ != this->impl_;
    }
};

此代码有效,但它有使用dynamic_cast的缺点,我需要在转换操作中处理错误。

当一个具体类的函数试图使用一些内部信息(在抽象类级别不可用)来执行任务时,会发生这种一般性问题。

有没有更好的方法来解决这类问题?

干杯

10 个答案:

答案 0 :(得分:5)

您不希望在基类中实现等于运算符==!=。基类不知道后代的数量或内容。

例如,使用Shape类示例:

struct Shape
{
  virtual bool equal_to(const Shape& s) const = 0;  // Makes Shape an abstract base class.
  bool operator==(const Shape& s) const
  {
     return equal_to(s);
  }
  bool operator!=(const Shape& s) const
  {
     return !equal_to(s);
  }
};

struct Square : public Shape
{
  bool equal_to(const Shape& s) const;
};

struct Circle : public Shape
{
  bool equal_to(const Shape& s) const;
};

struct Flower : public Shape
{
  bool equal_to(const Shape& s) const;
};

struct Cloud : public Shape
{
  bool equal_to(const Shape& s) const;
};

为了满足Shape类的相等运算符,每个后代必须实现equal_to方法。 但是等一下,Square如何知道其他Shape的类型?

在此示例中,Square类需要在引用上使用dynamic_cast来强制转换为Square对象。当参数为CircleFlowerCloud或其他一些尚未定义的形状时,这将失败。

以下是您必须注意的有效概念:

Square my_square;
Cloud  my_cloud;
Shape * p_shape_1 = &my_square;  // Square is-a Shape, so this is legal.
Shape * p_shape_2 = &my_cloud;   // Cloud inherits from Shape, so this is legal.

if (*p_shape_1 == *p_shape_2)  // Legal syntax because of Shape::operator==().
{ //???}

上面的比较会调用令人讨厌的行为。这可以在仅在形状上操作的通用函数中实现。

分辨率

更改设计。您永远不应该在基类中放置一个与基类相比较的公共比较运算符。讨厌。

后代与后代相比。时期。正方形到广场,鲜花到鲜花和圆圈到圆圈。在后代类中实现比较运算符。

比较基类内容:如果您在基类中有共享内容,请实现一个受保护的方法来仅比较基类方法:

struct Shape
{
  protected:
    bool equal_shape_content(const Shape& s) const;
};

这将使您的程序更加健壮,因为它只将Shape的内容与另一个Shape进行比较。这是你可以保证的一切。另请参阅基类切片

答案 1 :(得分:2)

或者如果那是不可能的(例如,因为在定义Abstact类时不知道SomeObject),你将不得不完全处理它..

class Abstract
{
  public:
     //to make the syntax easier let's use a raw pointer
     virtual bool operator!=(const Abstract* other) = 0;
};

class Implementation : public Abstract
{
    SomeObject impl_; //that already implement the operator!=
  public:
    bool operator!=(const Abstract* other)
    {
      // remember that dynamic cast can't remove a 'const' qualifier
      const Implementation* o = dynamic_cast<const Implementation*>(other);
      return (o == NULL || o->impl_ != this->impl_);
    }
};

答案 2 :(得分:2)

你的问题中的内容是行不通的。在下面编写一个简单的测试应用程序会在我现在可用的每个编译器上生成相同的输出(MSVC,GCC):

class Abstract
{
public:
    virtual bool operator!=(const Abstract* rhs) = 0;
};

class Implementation : public Abstract
{
public:
    virtual bool operator!=(const Abstract* rhs)
    {
        std::cout << "Implementation::operator!= called" << std::endl;
        return false;
    }
};

int main()
{
    Abstract* pA = new Implementation;
    Abstract* pB = new Implementation;
    if (pA != pB)
    {
        std::cout << "Compared the pointers" << std::endl;
    }
    return 0;
}

输出:

Compared the pointers

你不能重载operator!=以这种方式比较2个指针。您可以执行以下操作:

bool operator!=(const Abstract* lhs, const Abstract* rhs)
{
    return lhs->SomeVirtualFunctionThatReturnsUsefulInformation() != rhs->SomeVirtualFunctionThatReturnsUsefulInformation()
}

但我强烈建议不要这样做,因为它不可能比较你的指针(指针的实际值)。

相反,你最好使用这种方法:

class Abstract
{
public:
    virtual bool Equals(const Abstract* rhs) = 0;
};

class Implementation : public Abstract
{
public:
    bool operator==(const Implementation& rhs)
    {
        return this->SomeFunctionThatReturnsUsefulInformation() == rhs.SomeFunctionThatReturnsUsefulInformation();
    }

    virtual bool Equals(const Abstract* rhs)
    {
        Implementation* pRHS = dynamic_cast<Implementation*>(rhs);
        if (!pRHS) // not an Implementation of Abstract
        {
      return false;
        }
  return *this == *pRHS;
    }
};

int main()
{
    Abstract* pA = new Implementation;
    Abstract* pB = new Implementation;
    if (pA->Equals(pB))
    {
        std::cout << "Works" << std::endl;
    }
    return 0;
}

答案 3 :(得分:1)

如何向Abstract类添加虚拟实现getter,以便您可以简单地说

return other->GetImp() != GetImp();

答案 4 :(得分:1)

您提供的示例有很多问题,但一般来说,您可以使用非虚拟重载,避免使用强制转换:

class Implementation 
{     
  SomeObject impl_; //that already implement the operator!=   
public:     
  virtual bool NotEqual(const Abstract* other)     
  {       
    Implementation *pImplementation=dynamic_cast<Implementation*>(other);

    return !pImplementation || 
           other->impl_ != this->impl_;     
  } 

  bool NotEqual(const Implementation* other)     
  {       
    return other->impl_ != this->impl_;     
  } 
};

现在,如果您在呼叫站点有Implementation *,则会调用重载函数,并且不需要强制转换。

答案 5 :(得分:0)

您可以在抽象级别实现虚拟getter:

class Abstract
{
  public:
     //to make the syntax easier let's use a raw pointer
     virtual bool operator!=(const Abstract* other) = 0;

  private:
     virtual SomeObject& getImpl() = 0;
};

class Implementation : public Abstract
{
    SomeObject impl_; //that already implement the operator!=
  public:
    bool operator!=(const Abstract* other)
    {
      return other->getImpl() != this->getImpl();
    }

  private:
    SomeObject& getImpl()
    {
      return impl_;
    }
};

答案 6 :(得分:0)

我想最实用的替代方法是在比较之前检查类型:

bool operator!=( const Abstract * other )
{
    if( other == NULL || typeid( *other ) != typeid( Implementation ) )
        return true;

    return static_cast<Implementation *>( other )->_impl != _impl;
}

答案 7 :(得分:0)

当尝试执行涉及两个或更多个多态对象的操作时,一个上的虚拟方法和另一个上的dynamic_cast通常是您的最佳选择。还有其他方法,但它们必然很复杂。

我还会给虚拟方法一个与运算符不同的名称。至少减少了我的困惑。

class Abstract
{
public:
    virtual ~Abstract();
    virtual bool equal_to(const Abstract&) const = 0;
};
inline bool operator==(const Abstract& x, const Abstract& y)
{
    return x.equal_to(y);
}
inline bool operator!=(const Abstract& x, const Abstract& y)
{
    return !(x==y);
}

class Implementation { /*...*/ };

bool Implementation::equal_to(const Abstract& obj) const
{
    const Implementation* der_obj = dynamic_cast<const Implementation*>(&obj);
    if (!der_obj) return false;
    return impl_ == der_obj->impl_;
}

请务必检查dynamic_cast的结果是否为NULL。据推测,可能会有多个实现类(如果不是现在,将来)。

如果Implementation::equal_to有任何子类,我上面的Implementation可能也可能不正确。

答案 8 :(得分:0)

要实现你的operator!=,你需要某种双重调度:就是这样,你需要根据两个对象的类型调用一个函数。

为了避免您谈到的问题(始终使用dynamic_cast),只需重载operator!=,以便它占用Implementation个对象。


我通常选择的解决方案类似于您提议的解决方案。

任何派生类'operator!=检查Abstract对象的类型是否与实现类型相同,如果它将Abstract对象强制转换为Implementation并且运行你的支票。

我建议使用type_info + static_cast代替dynamic_cast,因为后者速度较慢。


可以使用C ++实现双重调度的其他方法,例如使用访问者模式(或许多其他方式),但在这种情况下我不会建议,因为它们更难以实现并为您提供其他优势不需要。


几点说明:

  • 您应该传递引用,为什么要在Abstract对象和指针之间定义比较运算符?

  • 该代码根本不起作用。例如,在您的代码段中Implementation不会继承Abstract,因此您无法使用dynamic_cast

答案 9 :(得分:0)

它可以在没有dynamic_cast的情况下完成,并且可以正常工作但是使用知道其所有子类的基类的“邪恶”。 (使用双重调度的访问者模式也是典型的问题。)

您可以通过拥有一个知道所有实现的外部类而不是在实际的基类中使用它来解决这个问题。

使用简单模型,我将使用Abstract和Impl1和Impl2。我将调用方法equals()

class Abstract
{
 public:
   virtual ~Abstract() {}
   virtual bool equals( const Abstract * other ) const =0;
   virtual bool equals_Impl1( const Impl1 * other ) const { return false; }
   virtual bool equals_Impl2( const Impl2 * other ) const { return false; }
};




class Impl1 : public Abstract
{
 public:
    bool equals( const Abstract * other ) const
    {
        return other->equals_Impl1( this );
    }
    bool equals_Impl1( const Impl1 * other ) const
    {
       // calculate and return true if they compare
    }
};

 // do the same with Impl2

不理想,因为它不是“可扩展的”,即你不能在不修改Abstract的情况下添加Impl3来支持它。