CRTP和多级继承 [英] CRTP and multilevel inheritance

查看:218
本文介绍了CRTP和多级继承的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的一位朋友问我如何使用CRTP替换多级继承中的多态。更准确地说,在这种情况下:

A friend of mine asked me "how to use CRTP to replace polymorphism in a multilevel inheritance". More precisely, in a situation like this:

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 */ }
}

我的朋友和我都知道CRTP不是多态性的替代品,但我们对可以使用这两种模式的情况感兴趣。 (为了这个问题,我们对每种模式的利弊都不感兴趣。)

My friend and I understand that CRTP is not a drop-in replacement for polymorphism but we are interested in cases where both patterns can be used. (For the sake of this question, we are not interested in pros and cons of each pattern.)


  1. 问题但事实证明,作者希望实现命名参数idiom 和他自己的答案重点关于这个问题比CRTP更多。另一方面,投票最多的答案似乎只是一个派生类方法在基类中调用它的同音词。

  1. This question has been asked before but it turned out the the author wanted to implement the named parameter idiom and his own answer focus on this problem more than on the CRTP. On the other hand, the most voted answer seems to be just about a derived class method calling its homonym in the base class.

我想出了一个答案(如下所示),它有很多样板代码,我想知道是否有更简单的替代品。

I came up with an answer (posted below) which has quite a lot of boilerplate code and I wonder if there are simpler alternatives.


推荐答案

(1)层次结构中最顶层的类看起来像:

(1) The topmost class in the hierarchy looks like:

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()已设为受保护

A<T>::foo() behaves similarly to a pure virtual method in the sense that it doesn't have a "default implementation" and calls are directed to derived classes. However, this doesn't prevent A<T> from being instantiated as a non base class. To get this behavior A<T>::~A() is made protected.

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

Remark: Unfortunately a GCC bug turns special member functions public when = default; is used. In this case, one should used

protected:
    ~A() {}

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

Still, protecting the destructor is not enough for the cases where a call to a constructor is not matched by a call to the destructor (this might happen via operator new). Hence, it's advisable to protect all constructors (including copy- and move-constructor) as well.

当实例化 A< T> 并且 A< T> :: foo()应该表现得像非纯虚方法,然后 A 应该类似于下面的模板类 B

When instantiations of A<T> should be allowed and A<T>::foo() should behave like a non-pure virtual method, then A should be similar to the template class B below.

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

(2) Classes in the middle of the hierarchy (or the topmost one, as explained in the paragraph above) look like:

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();
  }

};

构造函数和析构函数是公共的, T 默认为 void 。这允许 B<> 类型的对象在层次结构中派生得最多,并使其合法化:

Constructors and destructors are public and T defaults to void. This allows objects of type B<> to be the most derived in the hierarchy and makes this legal:

B<> b;
b.foo();

另请注意, B< T> :: foo()表现为一种非纯虚方法,如果 B< T> 是派生程度最高的类(或者更确切地说,如果 T void ),然后 b.foo(); 调用默认实现 foo()(输出 B :: foo())。如果 T 不是 void ,则调用将定向到派生类。这是通过标签调度来完成的。

Notice also that B<T>::foo() behaves as a non pure virtual method in the sense that, if B<T> is the most derived class (or, more precisely, if T is void), then b.foo(); calls the "default implementation of foo()" (which outputs B::foo()). If T is not void, then the call is directed to the derived class. This is accomplished through tag dispatching.

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

The test &B::foo == &T::foo is required to avoid an infinite recursive call. Indeed, if the derived class, T, doesn't reimplement foo(), the call static_cast<const T*>(this)->foo(); will resolve to B::foo() which calls B::foo_impl(std::false_type) again. Furthermore, this test can be resolved at compile time and the code is either if (true) or if (false) and the optimizer can remove the test altogether (e.g. GCC with -O3).

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

(3) Finally, the bottom of the hierarchy looks like:

class C : public B<C> {

public:

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

};

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

Alternatively, one can remove C::foo() entirely if the inherited implementation (B<C>::foo()) is adequate.

请注意, C :: foo()类似于最终方法,因为调用它不会将调用重定向到派生类(如果有)。 (要使其成为非最终版,应使用类似 B 的模板类。)

Notice that C::foo() is similar to a final method in the sense that calling it does not redirected the call to a derived class (if any). (To make it non final, a template class like B should be used.)

(4 )另见:

如何在使用CRTP时避免错误?

这篇关于CRTP和多级继承的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆