实现基类比较的正确方法是啥?

Posted

技术标签:

【中文标题】实现基类比较的正确方法是啥?【英文标题】:What is the correct way to implement the comparison for a base class?实现基类比较的正确方法是什么? 【发布时间】:2010-09-18 19:22:36 【问题描述】:

我有一个基类

class Animal 

具有纯虚函数和一组派生类

class Monkey : public Animal 
class Snake : public Animal

我想实现一个比较操作,这样,如果我在代码中遇到两个指向 Animals 的指针

Animal* animal1
Animal* animal2

我可以将它们相互比较。如果animal1 和animal2 属于不同的派生类,则比较应该产生错误。如果它们属于相同的派生类,则应返回比较运算符的输出。

有人可以指出实现此功能的好方法吗?

【问题讨论】:

【参考方案1】:

哇,很多其他的答案都是完全没有必要的。 dynamic_cast-它存在,使用它。

class Animal 
public:
    virtual bool operator==(const Animal& other) = 0;
    virtual ~Animal() = 0;
;
template<class T> class AnimalComp : public Animal 
public:
    virtual bool operator==(const Animal& ref) const 
        if (const T* self = dynamic_cast<const T*>(&ref)) 
            return ((T*)this)->operator==(*self);
        
        return false;
    
    virtual bool operator!=(const Animal& ref) const 
        if (const T* self = dynamic_cast<const T*>(&ref)) 
            return ((T*)this)->operator!=(*self);
        
        return true;
    
;
class Monkey : public AnimalComp<Monkey> 
public:
    virtual bool operator==(const Monkey& other) const 
        return false;
    
    virtual bool operator!=(const Monkey& other) const 
        return false;
    
;
class Snake : public AnimalComp<Snake> 
public:
    virtual bool operator==(const Snake& other) const 
        return false;
    
    virtual bool operator!=(const Snake& other) const 
        return false;
    
;

编辑:在我的自动模板实现之前鞠躬!

编辑编辑:我做的一件事就是忘记将它们标记为 const,这对我来说是错误的。我不会为没有这样做而道歉!= 因为,让我们面对现实吧,实现它完全是轻而易举的事。

更多编辑:这不是如何编写 != 或 == 的示例,而是如何使用 CRTP 的示例。

【讨论】:

是的,使用 operator== 重载会更好。 小错误,需要const monkey* p = dynamic_cast&lt;const Monkey*&gt;(&amp;other)return *this == *p ...引用不能转换为bool @imaginaryboy:是的,我把它改成了一个更好的解决方案。 非常漂亮,非常感谢。还有一个问题:为什么 Animal 的析构函数是纯虚拟的? - 这样它不会编译。如果你用 virtual ~Animal() 替换它,它可以完美地工作。 它在我的编译器上编译得很好。 :P 说真的,这并不重要,我只是出于习惯把它变成了纯虚拟的。【参考方案2】:

实现这一点的一种方法是使用双重调度来区分“同一类”和“不同类”:

class Monkey;
class Snake;

class Animal 
public:
  virtual bool compare_impl(const Animal*) const  return false; 
  virtual bool compare_impl(const Monkey*) const  return false; 
  virtual bool compare_impl(const Snake*) const  return false; 
  virtual bool compare(const Animal* rhs) const =0;
;

class Monkey : public Animal 
private:
  /* Override the default behaviour for two Monkeys */
  virtual bool compare_impl(const Monkey*) const  /* compare two Monkey's */ 
public:
  /* Let overload-resolution pick the compare_impl for Monkey and let virtual dispatch select the override in the dynamic type of rhs */
  virtual bool compare(const Animal* rhs) const  return rhs->compare_impl(this); 
;

class Snake : public Animal 
private:
  /* Override the default behaviour for two Snakes */
  bool compare_impl(const Snake*) const  /* compare two Snakes */ 
public:
  /* Let overload-resolution pick the compare_impl for Monkey and let virtual dispatch select the override in the dynamic type of rhs */
  virtual bool compare(const Animal* rhs) const  return rhs->compare_impl(this); 
;

【讨论】:

你能解释一下使用 Animal::compare_impl;方法?到目前为止,我只在“using namespace foo”中看到了关键字“using” @Hans:它将基类的标识符带入派生类的作用域,防止派生类重载隐藏基类函数。 他的“使用”声明做了两件事:1) 将基类 compare_impl 的访问权限从受保护更改为私有,以及 2) 使 Animal::compare_impl 可访问,因为它被隐藏了派生类的compare_impl 声明。 哦,现在我明白了在比较函数内部,两边是相反的(this->compare_impl(rhs) 变成 rhs->compare_impl(this))。这个解决方案唯一困扰我的是我需要将相同的函数比较放入每个派生类中。有没有办法避免这种情况? @Hans:你可以,但是你必须在Animal 中为每个派生类包含一个virtual bool compare_impl(const Derived*) const return false; 成员函数。这有一个更大的缺点,Animal 突然必须知道所有派生类。使这种技术起作用的两件事首先是参数的反转(第一个函数调用参数的成员),其次是所有相关的重载都存在于进行反向调用的地方。【参考方案3】:

由于没有与这两个指针关联的静态类型信息,您将需要使用RTTI。您可以比较typeid operator类型的结果,以确定对象是否属于同一类型。

另一种方法是将您自己的类型 ID 添加到 Animal 类。添加另一个虚函数并让派生类返回唯一标识该类型的内容。您可以使用枚举,也可以使用类型的名称作为字符串。但是,如果您可以使用它,那么 RTTI 会更好,恕我直言。

【讨论】:

+1。此外 - RTTI 会产生一些运行时开销(显然),但不会太多。如果您的应用程序有大量的类,而您只需要在几个地方使用 RTTI,我会选择使用专用功能进行类型识别的您自己的解决方案。但是,如果 RTTI 的使用很普遍 - 你绝对应该使用它而不是使用你自己的方法。 我认为双重分派可能是比手动摆弄std::type_info 对象更好的方法。一旦你知道它是正确的类型,你会怎么做?您仍然必须将指针转换为派生类的指针。而且由于 C++ 只支持最小的 RTTI,您可能必须切换一种类型。切换类型通常强烈表明有人最好使用virtual 函数。 @sbi 同意,实际上。我花了太多时间不情愿地编写“Java 方式”(tm)。它显然影响了我的 C++。 有人对此有实际可行的双重调度解决方案吗?下面的没有。【参考方案4】:

使用 type_info 类。它定义了 operator== 返回两种类型是否描述相同的类型。 在这里您可以找到参考: http://www.cplusplus.com/reference/std/typeinfo/type_info/

【讨论】:

【参考方案5】:

这是我使用的一个小技巧(我希望它也对你有用)。 我在 Animal 中添加了如下私有方法,并在每个派生类中 OVERRIDE (我知道,这有点麻烦,但比 RTTI 快)

class Animal 
protected:

 virtual const void* signature() const 
 
  static bool dummy;
  return &dummy;
 
...



class Monkey : public Animal 
private:
 virtual const void* signature() const 
 
  static bool dummy;
  return &dummy;
 
...

现在要查看 2 个指针(a 和 b)是否属于同一类,只需检查

a->signature()==b->signature()

这不是一个真正的解决方案,它是一个技巧,但它只适用于 2 个虚拟方法调用(每个指针 1 个),所以它相当快。

【讨论】:

以上是关于实现基类比较的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在类库项目的基类中定义 HttpClient 实例的最佳方法是啥?

对虚函数进行重载是啥意思?

向 Django 添加文本区域的正确方法是啥?

实现 QThread 的正确方法是啥...(请举例...)

使用通用虚函数实现的任意组合实现派生类的正确方法是啥?

在机器对机器通信场景中使用的正确字符串比较值是啥?