【问题标题】:Implementing polymorphic operator==() in C++ idiomatic way以 C++ 惯用方式实现多态运算符 ==()
【发布时间】:2015-06-12 18:48:00
【问题描述】:

我认为我需要的是一个纯虚函数,除了基类确实有一个派生类不能默认的实现(并且这个规则必须传播)。 final的反义词?

我有一些从公共基础派生的类型。基类是一种有用的类型,一些派生类型是从其他派生类型派生的。我将只使用对基类的引用,但我需要一个virtual operator==(),它可以看穿它并调用适合每种情况的手动比较。 operator==() 的一种二维 vtable。

重要的是,实现不要传播到派生类,因为意外发生这种情况可能会导致不兼容类型与基类实现之间的比较兼容类型,这可能产生误报。

我的意思是让特定的功能等效案例比较相等,尽管它们在不同的类中表示。不过,我预计问题会延伸到其他操作,也许我对operator==() 的使用在这里不合适。

我并没有声称自己知道 C++——我只是一个 C hack,试图成为惯用的。

这是我到目前为止的工作:

class base;
class foo;
class bar;
class baz;

#define COMPARE public: \
  virtual bool equal(base const &p) const; \
  virtual bool equal(foo const &p) const; \
  virtual bool equal(bar const &p) const; \
  virtual bool equal(baz const &p) const; \
  virtual bool operator==(base const &p) const { return p.equal(*this); }

class base {
  int a_;

 public:
  base(int a) : a_(a) {}

  COMPARE
};

class foo : public base {
  int b_;

 public:
  foo(int a, int b) : base(a), b_(b) {}

  COMPARE
};

class bar : public base {
  int c_;

 public:
  bar(int a, int c) : base(a), c_(c) {}

  COMPARE
};

class baz : public bar {
  int d_;

 public:
  baz(int a, int c, int d) : bar(a, c), d_(d) {}

  COMPARE
};

现在,感谢COMPARE,所有T::equal() 都必须实现,并且不允许它们依赖于早期的实现。此外,每个类都有自己的operator==(),它为自己的类型(而不是基类类型)调用适当的equal()。

我想要的是以COMPARE 现在的方式强制执行这些规则,但不需要记住每个派生类都必须引用宏,并且理想情况下(要成为 C++ 惯用的)不使用宏全部。

正确的 C++ 方法是什么?

【问题讨论】:

  • 实际上,如果有一个始终返回 false 的默认实现,并且能够覆盖不需要传播到派生类的覆盖,那么 真的 会很棒.
  • 你能强调一下你想要完成什么吗?
  • @user64985,我希望让基类对派生类施加规则,并且避免必须记住在每个派生类中使用 C 宏来重新声明这些规则。
  • 这不仅仅是like一个纯虚函数,而是一个纯虚函数。没有法律禁止定义纯虚函数,事实上有时它是必要的(析构函数)。
  • this 可能会给你一些想法

标签: c++ macros virtual-functions


【解决方案1】:

我还在学习,但你描述的听起来很像它可能需要双重调度和/或访问者模式。

对于双重调度,类似于:

class base;
class foo;
class bar;
class baz;


class base {
    int a_;
public:
    base(int a) : a_(a) {}
    virtual bool operator==(const base&) const =0;
    virtual bool operator==(const foo&) const =0;
    virtual bool operator==(const bar&) const =0;
    virtual bool operator==(const baz&) const =0;
};

class foo : public base {
    int b_;
public:
    foo(int a,int b) : base(a),b_(b) {}
    bool operator==(const base&) const override;
    bool operator==(const foo&) const override;
    bool operator==(const bar&) const override;
    bool operator==(const baz&) const override;
};

class bar : public base {
    int c_;
public:
    bar(int a,int c) : base(a),c_(c) {}
    bool operator==(const base&) const override;
    bool operator==(const foo&) const override;
    bool operator==(const bar&) const override;
    bool operator==(const baz&) const override;
};

class baz : public bar {
    int d_;
public:
    baz(int a,int c,int d) : bar(a,c),d_(d) {}
    bool operator==(const base&) const override;
    bool operator==(const foo&) const override;
    bool operator==(const bar&) const override;
    bool operator==(const baz&) const override;
};

这看起来很像上面介绍的宏选项。 :)

从 TC++PL 第 4 版,第 22.3.1 节谈到双重分派,其中提到了可能宁愿使用预先计算的查找表。类似的东西

bool equal(const base& b1,const base& b2)
    {
        auto i = index(type_id(b1),type_id(b2));
        return intersect_tbl[i](b1,b2);
    }

【讨论】:

  • 对于初学者来说肯定很复杂,但我为多次调度做了this,OP的案例似乎与IsEqual访问者有关。
【解决方案2】:

除了基类确实有一个派生类不能默认的实现(并且这个规则必须传播)

如果这是唯一的问题,那么正如 Ben Voigt 在他的评论中提到的那样 - 这不是问题,因为可以实现纯虚函数。

另外,一旦派生类被覆盖,我需要相同的规则, 进一步的派生类不使用覆盖(如果它们必须 那么他们总是可以在自己的实现中明确地调用它)

如果是这种情况,那么“惯用的 C++ 方式”可能不会使用继承来建模“派生” - “进一步派生”。继承通常用于对可替换性或多态性建模,但在这里都不是。换句话说:模型“派生” - 按组合“进一步派生”。

【讨论】:

  • 我想我不明白你的意思。错过了哪里?
  • 不幸的是,我的基类和派生类具有它们应该继承的其他共同属性。另外,我确实需要一个兼容的底层引用类型,我可以全部使用它们(需要访问这些共享属性)。
  • @Jarod42 啊,不错的收获! :)
【解决方案3】:

所以这似乎是一种强制派生类提供特定功能的自己的实现的方法:

template<typename T> struct sweep : public T {
  template <class... Args> sweep(Args&&... args) : T(args...) { }
  virtual bool equal(base const &p) const = 0;
  virtual bool equal(foo const &p) const = 0;
  virtual bool equal(bar const &p) const = 0;
  virtual bool equal(baz const &p) const = 0;
  virtual bool operator==(base const &p) const = 0;
};

class base { ... };

class foo : public sweep<base> {
  int b_;

 public:
  foo(int a, int b) : sweep(a), b_(b) {}

  ...
};

它仍然要求派生类记住做一些特定的事情来约束自己——使用sweep模板从基类派生——但它至少是C++而不是C。

它还有一个优点是模板可以刷新默认实现而不是使它们成为纯虚拟的;像这样:

template<typename T> struct sweep : public T {
  ...
  virtual bool equal(base const &p) const { return false; }
  ...
};

因为没有进一步的指导,每次比较都应该失败。这实际上更接近我的需要——但不是我要求的。

【讨论】:

  • 如果有人知道为什么编译器不喜欢我超越模板构造函数以从 foo 构造函数到达 base 构造函数,那可能有助于解释我是如何陷入困境的可变参数模板。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-05
  • 1970-01-01
  • 2012-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多