【问题标题】:C++ comparator overrideC++ 比较器覆盖
【发布时间】:2021-09-13 23:44:22
【问题描述】:

假设我有一个基类和多个派生类(将来可能会更多)。 有没有办法覆盖派生类的比较器,或者达到相同的结果?

class Base {
 public:
  virtual bool operator==(const Base&) = 0;
};

class Version1 : public Base {
 public:
  bool operator==(const Derived&) {
    // do something
  }
};

class Version2 : public Base {
 public:
  bool operator==(const Derived&) {
    // do something
  }
};

// Could be more version derived class


int main() {
  Base *obj1 = new Version1;
  Base *obj2 = new Version1;
  std::cout << (*obj1 == *obj2) << std::endl;  // is there a way to do so?
}

具体来说,Base 的任何子类都可以与 Base 的任何其他子类相媲美,而无需宏和 dynamic_cast。

【问题讨论】:

  • operator==Derived 的那些用法应该分别是Version1Version2 吗?因为你没有任何名为Derived...
  • 并且要清楚的是,您的意图是 Base 的任何子类与 Base 的任何其他子类可比较,而特定子类可与该子类的其他实例可比较,但不是Base 的其他子类不是直系祖先或后代?因为那个纯虚拟声明是说任何Base 子类必须与任何其他Base 子类相当,即使它们是不同的类,只使用Base 提供的功能,这是一个奇怪的要求。
  • Base 的任何子类都可以与任何其他子类进行比较。

标签: c++ inheritance overriding


【解决方案1】:

不幸的是,C++ 不支持多动态调度,因此无法直接执行此操作——您最终需要使用 dynamic_cast 并在比较函数中进行额外检查:

class Base {
 public:
    virtual bool operator==(const Base&) const = 0;
};

class Version1 : public Base {
 public:
    bool operator==(const Base &b) const {
        if (auto *a = dynamic_cast<Version1 *>(&b)) {
            if (typeid(*this) != typeid(*a)) {
                // `a` is some other class derived from Version1
                return false; }
            // compare fields of *this and *a for equality
            return f1 == a->f1 && f2 == a->f2 && ...
        }
        return false;
    }

一个棘手的部分是typeid 测试,仅当将来可能有一些其他类从 Version1 派生时才需要。如果是 final (class Version1 final : public Base...) 则不需要 typeid 测试。

另一个细节是 operator== 函数上的 const ——您几乎总是需要它,以便可以比较(与)右值或常量。

【讨论】:

  • 啊这就是我的程序现在的样子...由于函数调用太多,我想知道是否可以用 if/else 分支替换它们。
  • “函数调用过多”是什么意思?太难阅读/理解/维护代码?太慢了? if/else 分支通常比函数调用慢。
  • 派生类大约有 10 个版本。每次我进行比较时,我都会执行类似 `` if (v1 == version1 && v2 == version1) {} else if (v1 == version1 && v2 == version2) {} ``
  • 不确定为什么要进行大量比较,除非您要比较很多不同的对象?但这似乎与您的问题没有任何联系。
  • 是的,每个版本的对象都应该相互比较。比较器包括==、等
【解决方案2】:

双重调度有效。

// Forward declaration
class Version1;

class Base {
 public:
  virtual bool operator==(const Base&) const = 0;
  virtual bool operator==(const Version1&) const = 0;
};

class Version1 : public Base {
 public:
  bool operator==(const Base& rhs) const override {
    return rhs == *this;
  }
  
  bool operator==(const Version1& rhs) const override {
    std::cout << "Version1 / Version1\n";
    return true;
  }
};


int main() {
  Base *obj1 = new Version1;
  Base *obj2 = new Version1;
  std::cout << (*obj1 == *obj2) << std::endl;
}

【讨论】:

    【解决方案3】:

    不确定这是否会满足您的需求,但您可以做到:

    class Version1 : public Base {
     public:
      bool operator==(const Base&) {
        // do something
      }
    };
    

    class Version2 : public Base {
     public:
      bool operator==(const Base&) {
        // do something
      }
    };
    

    这确保version1version2 不是抽象类,因此可以被实例化。


    好吧,既然我们知道真正的问题是什么,我可以尝试一个更好的答案。可以通过添加另一个虚函数来模拟所需的“双重调度”,我们称之为TaggedEquals,并使用颠倒的参数调用它,以及标识被传递对象类型的enum。这让我们陷入了这样一种情况:TaggedEquals 中的一个简单的switch 语句将完成这项工作。

    这里有一些代码来演示我在说什么:

    #include <iostream>
    
    class Base {
    public:
        virtual bool operator== (const Base& other) = 0;
    
        enum ParamType { D1, D2 };
        virtual bool TaggedEquals (const Base& other, enum ParamType paramtype) const = 0;
    };
    
    class Derived1 : public Base {
    public:
        bool operator== (const Base& other) override
        {
            return other.TaggedEquals (*this, D1);
        }
        
    private:
        bool TaggedEquals (const Base&, enum ParamType paramtype) const override
        {
            switch (paramtype)
            {
                case D1:
                    std::cout << "D1 == D1\n";
                    return true;
                case D2:
                    std::cout << "D2 == D1\n";
                    return false;
            }
            
            return false;
        }
    };
    
    class Derived2 : public Base {
    public:
        bool operator== (const Base& other) override
        {
            return other.TaggedEquals (*this, D2);
        }
    
    private:
        bool TaggedEquals (const Base&, enum ParamType paramtype) const override
        {
            switch (paramtype)
            {
                case D1:
                    std::cout << "D1 == D2\n";
                    return false;
                case D2:
                    std::cout << "D2 == D2\n";
                    return true;
            }
            
            return false;
        }
    };
    
    int main()
    {
        Base *d1a = new Derived1;
        Base *d1b = new Derived1;
        std::cout << (*d1a == *d1b) << "\n";
        Base *d2 = new Derived2;
        std::cout << (*d1a == *d2) << "\n";
    }
    

    输出:

    D1 == D1
    1
    D1 == D2
    0
    

    这种方法的优点是,如果您向enum 添加一个值,编译器会提醒您将其添加到您的switch 语句中。

    要实现operator&lt; 之类的东西,您将需要TaggedGreaterThanOrEqualTo 之类的东西(因为您交换了一轮参数),或者您可以使用TaggedGreaterThanTaggedEquals 来模拟它。

    【讨论】:

    • 好吧,我认为这对我的情况不起作用。我绝对需要两个 Base* 之间的比较,每次都没有丑陋的 dynamic_cast。
    • I tested it,它似乎有效。
    • 抱歉含糊不清。我也想要version1 == version1,不会决定为version1 == Base
    • 对不起,我不明白那个评论。
    • 在您的代码 sn-p 中,我希望 obj1 和 obj2 匹配带有签名 Version1::operator==(const Version1&amp;) 的运算符。您的实现将引导编译调用Version1::operator(const Base&amp;)
    【解决方案4】:

    您可以在同一接口的类之间实现外部模板比较运算符

    template<typename A, typename B>
    bool operator==(const A& a, const B& b)
    {
      static_assert(std::is_base_of_v<Base, A>, "class must derive from A");
      static_assert(std::is_base_of_v<Base, B>, "class must derive from B");
    
      // do your comparison
    }
    

    【讨论】:

      猜你喜欢
      • 2012-03-25
      • 1970-01-01
      • 1970-01-01
      • 2020-08-09
      • 2012-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多