【问题标题】:Elegant Object comparison优雅的对象比较
【发布时间】:2010-09-22 13:57:34
【问题描述】:

当比较两个(相同类型的)对象时,有一个比较函数接受同一类的另一个实例是有意义的。如果我在基类中将其实现为虚函数,那么函数的签名也必须在派生类中引用基类。解决这个问题的优雅方法是什么?比较不应该是虚拟的吗?

class A
{
    A();
    ~A();
    virtual int Compare(A Other);
}

class B: A
{
    B();
    ~B();
    int Compare(A Other);
}

class C: A
{
    C();
    ~C();
    int Compare(A Other);
}

【问题讨论】:

  • 不,使用 == 运算符是有意义的。这就是它的用途。不需要比较功能。
  • @jalf 我认为他想做大于、等于或小于比较,就像 strcmp() 一样。请注意,Compare() 返回的是 int,而不是 bool。

标签: c++ function virtual


【解决方案1】:

如果您的意思是 B 或 C 类中的 Compare() 应该始终传递 B 或 C 类的对象,无论签名说什么,您都可以使用指向实例的指针而不是实例,并尝试向下转换方法代码中的指针使用类似

int B::Compare(A *ptr)
{
   other = dynamic_cast <B*> (ptr);
   if(other)
      ...  // Ok, it was a pointer to B
}

(这种重载仅对那些在其父级状态中添加影响比较的东西的派生类是必需的。)

【讨论】:

    【解决方案2】:

    比较必须是反射性的,所以:

    let a = new A
    let b = new B (inherits from A)
    
    if (a.equals(b))
     then b.equals(a) must be true!
    

    所以a.equals(b) 应该返回 false,因为 B 可能包含 A 没有的字段,这意味着 b.equals(a) 可能是 false。

    因此,我猜在 C++ 中比较应该是虚拟的,并且您应该使用类型检查来查看参数与当前对象的“相同”类型。

    【讨论】:

    • 比较,不等于。想想 strcmp()。
    【解决方案3】:

    除了dynamic_cast,你还需要传递一个引用或者一个指针,可能是const。 Compare 函数也可能是 const。

    class B: public A
    {
        B();
        virtual ~B();
        virtual int Compare(const A &Other) const;
    };
    
    
    int B::Compare(const A &Other) const
    {
        const B *other = dynamic_cast <const B*> (&Other);
        if(other) {
            // compare
        }
        else {
            return 0;
        }
    }
    

    编辑:发布前必须编译...

    【讨论】:

    • 警告:在 B::Compare 中,Other 是一个对象,因此您的代码会尝试将对象转换为指针。此外,返回零意味着平等;我会提出一些例外
    • 在这种情况下你会提出一些例外吗?太可怕了。
    • 假设您可能想要比较继承自相同基类型的不同类型的 A 和 B 是完全合理的。您的示例似乎假设我们只关心在它们属于同一类型时进行比较。此外,目前还不清楚当它们属于不同类型时您打算发生什么。看起来你正在实现一个相等操作,而不是比较,你返回零表示不相等,大概是 1 表示相等。这与问题中提出的不同,后者是大于/小于操作。
    【解决方案4】:

    我在 C++ 中几乎没有这个问题。与 Java 不同,我们不需要从同一个根 Object 类继承所有类。在处理可比较(/值语义)类时,它们不太可能来自多态层次结构。

    如果在您的特定情况下确实需要,那么您又回到了双重调度/多方法问题。有多种解决方法(dynamic_cast、可能交互的函数表、访问者……)

    【讨论】:

      【解决方案5】:

      我会这样实现它:

      class A{
          int a;
      
      public:
          virtual int Compare(A *other);
      };
      
      
      class B : A{
          int b;
      
      public:
          /*override*/ int Compare(A *other);
      };
      
      int A::Compare(A *other){
          if(!other)
              return 1; /* let's just say that non-null > null */
      
          if(a > other->a)
              return 1;
      
          if(a < other->a)
              return -1;
      
          return 0;
      }
      
      int B::Compare(A *other){
          int cmp = A::Compare(other);
          if(cmp)
              return cmp;
      
          B *b_other = dynamic_cast<B*>(other);
          if(!b_other)
              throw "Must be a B object";
      
          if(b > b_other->b)
              return 1;
      
          if(b < b_other->b)
              return -1;
      
          return 0;
      }
      

      这与 .NET 中的 IComparable 模式非常相似,效果非常好。

      编辑:

      对上述内容的一个警告是a.Compare(b)(其中a 是A,b 是B)可能会返回相等,并且从不抛出异常,而@987654326 @ 将要。有时这是你想要的,有时不是。如果不是,那么您可能不希望您的 Compare 函数是虚拟的,或者您希望在基本 Compare 函数中比较 type_infos,如下所示:

      int A::Compare(A *other){
          if(!other)
              return 1; /* let's just say that non-null > null */
      
          if(typeid(this) != typeid(other))
              throw "Must be the same type";
      
          if(a > other->a)
              return 1;
      
          if(a < other->a)
              return -1;
      
          return 0;
      }
      

      请注意,派生类的Compare 函数不需要更改,因为它们应该调用基类的Compare,其中将发生type_info 比较。但是,您可以将覆盖的 Compare 函数中的 dynamic_cast 替换为 static_cast

      【讨论】:

      • 存在的问题是,如果对象的 B 部分不同,但 A 部分相同,则返回相等。
      • @coppro,我不同意。请记住,0 表示相等。
      • 在所有建议的解决方案中,我最喜欢这个解决方案,但它假设 a 的值比 b 的值更重要,并且 b 仅在两个 a 相等时才重要。这可能是真的,但这是一个强有力的假设,而且并非总是如此。不幸的是,我仍然认为这个问题的唯一答案是“视情况而定”。
      【解决方案6】:

      我可能会这样做:

      class A
      {
       public:
        virtual int Compare (const A& rhs) const
        {
          // do some comparisons
        }
      };
      
      class B
      {
       public:
        virtual int Compare (const A& rhs) const
        {
          try
          {
            B& b = dynamic_cast<A&>(rhs)
            if (A::Compare(b) == /* equal */)
            {
              // do some comparisons
            }
            else
              return /* not equal */;
          }
          catch (std::bad_cast&)
          {
            return /* non-equal */
          }
        }
      };
      

      【讨论】:

      • 还有一个问题,当类型不同时返回“不等于”,返回什么?您说“不等于”,但您对“不等于”的选择是 -1 表示大于或 1 表示小于。在这种情况下,两者都没有意义。此外,就像“P-daddy 的解决方案”一样,这假设 B 型的值不能覆盖 A 型。考虑比较动物的“凶猛”,其中 A 型是“动物”,仅比较力量,B 型是“捕食者” ”,无论力量如何,它(为了争论)总是比其他动物更凶猛。B的价值高于A。
      【解决方案7】:

      我建议不要将其设为虚拟。唯一的缺点是,如果类不相同,您必须明确说明要使用哪个比较。但是因为你必须这样做,所以你可能会发现一个错误(在编译时),否则可能会导致运行时错误......

      class A
      {
        public:
          A(){};
          int Compare(A const & Other) {cout << "A::Compare()" << endl; return 0;};
      };
      
      class B: public A
      {
        public:
          B(){};
          int Compare(B const & Other) {cout << "B::Compare()" << endl; return 0;};
      };
      
      class C: public A
      {
        public:
          C(){};
          int Compare(C const & Other) {cout << "C::Compare()" << endl; return 0;};
      };
      
      int main(int argc, char* argv[])
      {
          A a1;
          B b1, b2;
          C c1;
      
          a1.Compare(b1);     // A::Compare()
          b1.A::Compare(a1);  // A::Compare()
          b1.Compare(b2);     // B::Compare()
          c1.A::Compare(b1);  // A::Compare()
      
          return 0;
      }
      

      【讨论】:

        【解决方案8】:

        这取决于 A、B 和 C 的预期语义以及 compare() 的语义。比较是一个抽象的概念,不一定有一个正确的含义(或任何含义,就此而言)。这个问题没有唯一的正确答案。

        这里有两种情况,比较意味着具有相同类层次结构的两个完全不同的事物:

        class Object 
        {
            virtual int compare(const Object& ) = 0;
            float volume;
        };
        
        class Animal : Object 
        {
            virtual int compare(const Object& );
            float age;
        };
        
        class Zebra  : Animal 
        {
            int compare(const Object& );
        };
        

        我们可以考虑(至少)两种比较两个斑马的方法:哪个更老,哪个体积更大?两种比较都是有效且易于计算的;不同之处在于我们可以用体积来比较斑马和其他任何物体,但我们只能用年龄来比较斑马和其他动物。如果我们希望 compare() 实现年龄比较语义,那么在 Object 类中定义 compare() 没有任何意义,因为语义不是在层次结构的这个级别定义的。值得注意的是,这些场景都不需要任何转换,因为语义是在基类级别定义的(比较体积时是 Object,比较年龄时是 Animal)。

        这引发了一个更重要的问题——某些类不适合单个包罗万象的 compare() 函数。通常,实现多个明确说明正在比较的内容的函数更有意义,例如 compare_age() 和 compare_volume()。这些函数的定义可以发生在继承层次结构中语义变得相关的点,并且将它们适应子类应该是微不足道的(如果需要适应的话)。使用 compare() 或 operator==() 的简单比较通常只适用于正确语义实现明显且明确的简单类。

        长话短说......“这取决于”。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多