c++ - CRTP 和多级继承

标签 c++ inheritance c++11 crtp static-polymorphism

我的一个 friend 问我“如何使用 CRTP 替换多级继承中的多态性”。更准确地说,在这样的情况下:

struct A {

  void bar() {
    // do something and then call foo (possibly) in the derived class:
    foo();
  }

  // possibly non pure virtual
  virtual void foo() const = 0;
}

struct B : A {
  void foo() const override { /* do something */ }
}

struct C : B {
  // possibly absent to not override B::foo().
  void foo() const final { /* do something else */ }
}

我和我的 friend 都知道 CRTP 不是多态性的直接替代品,但我们对可以同时使用这两种模式的情况感兴趣。 (为了这个问题,我们对每种模式的优缺点不感兴趣。)

  1. 这个 question之前有人问过,结果作者想实现命名参数成语和他自己的answer更多地关注这个问题而不是 CRTP。另一方面,投票最多的answer似乎只是一个派生类方法在基类中调用其同音词。

  2. 我想出了一个答案(发布在下面),其中包含相当多的样板代码,我想知道是否有更简单的替代方案。

最佳答案

(1) 层次结构中最顶层的类如下所示:

template <typename T>
class A {

public:

  void bar() const {
    // do something and then call foo (possibly) in the derived class:
    foo();
  }

  void foo() const {
    static_cast<const T*>(this)->foo();
  }

protected:

  ~A() = default;

  // Constructors should be protected as well.

};

A<T>::foo()其行为类似于纯虚方法,因为它没有“默认实现”并且调用被定向到派生类。然而,这并不妨碍 A<T>从被实例化为非基类。要获得此行为 A<T>::~A()制作 protected .

备注:不幸的是 GCC bug= default; 时公开特殊成员函数用来。在这种情况下,应该使用

protected:
    ~A() {}

不过,对于对构造函数的调用与对析构函数的调用不匹配的情况(这可能通过 operator new 发生),保护析构函数是不够的。因此,建议同时保护所有构造函数(包括复制构造函数和移动构造函数)。

A<T> 的实例化时应该被允许和A<T>::foo()应该表现得像一个非纯虚方法,然后 A应该类似于模板类B下面。

(2) 层次结构中间的类(或最上面的类,如上一段所述)如下所示:

template <typename T = void>
class B : public A<B<T>> { // no inherinace if this is the topmost class

public:

  // Constructors and destructor

  // boilerplate code :-(
  void foo() const {
    foo_impl(std::is_same<T, void>{});
  }

private:

  void foo_impl(std::true_type) const {
    std::cout << "B::foo()\n";
  }

  // boilerplate code :-(
  void foo_impl(std::false_type) const {
    if (&B::foo == &T::foo)
      foo_impl(std::true_type{});
    else
      static_cast<const T*>(this)->foo();
  }

};

构造函数和析构函数是公共(public)的,T默认为 void .这允许 B<> 类型的对象成为层次结构中最派生的并使其合法:

B<> b;
b.foo();

另请注意B<T>::foo()在某种意义上表现为非纯虚方法,如果 B<T>是派生度最高的类(或者,更准确地说,如果 Tvoid ),那么 b.foo();调用“默认实现 foo()”(输出 B::foo())。如果 T不是 void ,然后调用被定向到派生类。这是通过标签调度完成的。

测试&B::foo == &T::foo需要避免无限递归调用。确实,如果是派生类,T , 不重新实现 foo() , 来电static_cast<const T*>(this)->foo();将解析为 B::foo()调用 B::foo_impl(std::false_type)再次。此外,这个测试可以在编译时解决,代码是 if (true)if (false)并且优化器可以完全删除测试(例如带有 -O3 的 GCC)。

(3) 最后,层次结构的底部看起来像:

class C : public B<C> {

public:

  void foo() const {
    std::cout << "C::foo()\n";
  }

};

或者,可以删除 C::foo()如果继承的实现 ( B<C>::foo() ) 就足够了。

请注意 C::foo()类似于 final 方法,调用它不会将调用重定向到派生类(如果有)。 (为了使它不是最终的,应该使用像 B 这样的模板类。)

(4)另见:

How to avoid errors while using CRTP?

关于c++ - CRTP 和多级继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18174441/

相关文章:

c++ - std::unordered_(set|map) 基于范围的删除的真实用例是什么?

C++ 容器-如果为类型定义了运算符<<,则列出成员?

c++ - 如何使模板类的一个实例与同一模板的另一个实例成为 friend

c++ - ios::app 和用于输入的 fstream 之间的交互

java - Java中的继承[子类声明为 protected 时出错]

swift - 如何修复 swift 3 嵌套的一般错误

c++11 - g++ 4.8.* std::chrono 未声明

c++ - 输入从指针接受空格时出现问题

c++ - 动态初始化和使用 C++ STL 列表数组

python - 如何通过 Python 中的子类实例访问基类变量?