关于CRTP静态多态性的困惑

use*_*849 9 c++ crtp

我正试着绕着CRTP.有一些很好的资料来源,包括这个论坛,但我认为我对静态多态性的基础知识有些困惑.查看以下维基百科条目:

template <class T> 
struct Base
{
    void implementation()
    {
        // ...
        static_cast<T*>(this)->implementation();
        // ...
    }

static void static_func()
{
    // ...
    T::static_sub_func();
        // ...
    }
};

struct Derived : public Base<Derived>
{
    void implementation();
    static void static_sub_func();
};
Run Code Online (Sandbox Code Playgroud)

我理解这有助于我在派生类中使用不同的implementation()变体,有点像编译时虚函数.但是,我的困惑是我觉得我不能有像这样的功能

void func(Base x){
    x.implementation();
}
Run Code Online (Sandbox Code Playgroud)

就像我使用普通继承和虚函数一样,由于Base被模板化,但我必须指定

func(Derived x)
Run Code Online (Sandbox Code Playgroud)

或使用

template<class T> 
func(T x)
Run Code Online (Sandbox Code Playgroud)

那么CRTP实际上在这个上下文中给我带来了什么,而不是简单地在Derived :: Base中简单地隐藏/实现该方法?

struct Base
{
    void implementation();

struct Derived : public Base
{
    void implementation();
    static void static_sub_func();
};
Run Code Online (Sandbox Code Playgroud)

Nir*_*man 10

问题在于CRTP作为"静态多态"的描述对于CRPT实际用于什么并不是真正有用或准确.多态性实际上只是拥有完成相同接口或契约的不同类型; 这些不同类型如何实现该接口与多态是正交的.动态多态看起来像这样:

void foo(Animal& a) { a.make_sound();  } //  could bark, meow, etc
Run Code Online (Sandbox Code Playgroud)

其中Animal是一个基类提供虚拟make_sound方法,即Dog,Cat等,覆盖.这是静态多态性:

template <class T>
void foo(T& a) { a.make_sound(); }
Run Code Online (Sandbox Code Playgroud)

就是这样.您可以调用foo定义make_sound方法的任何类型的静态版本,而无需继承基类.呼叫将在编译时解决(即您不会支付vtable呼叫)​​.

那么CRTP在哪里适合?CRTP真的不是关于接口,所以它不是关于多态的.CRTP旨在让您更轻松地实现工作.让CRTP变得神奇的是它可以直接将东西注入到一个类型的接口中,并完全了解派生类型提供的所有内容.一个简单的例子可能是:

template <class T>
struct MakeDouble {
    T double() {
        auto& me = static_cast<T&>(*this);
        return me + me;
};
Run Code Online (Sandbox Code Playgroud)

现在任何定义加法运算符的类也可以给出一个double方法:

class Matrix : MakeDouble<Matrix> ...

Matrix m;
auto m2 = m.double();
Run Code Online (Sandbox Code Playgroud)

CRTP完全有助于实现,而不是接口.因此,不要过于担心它通常被称为"静态多态".如果你想了解CRTP可以用于什么的真实规范示例,请考虑Andrei Alexandrescu的Modern C++设计的第1章.虽然,慢慢来:-).


Rei*_*ica 5

CRTP的优点只有在涉及多个功能时才会变得明显.考虑这段代码(没有CRTP):

struct Base
{
  int algorithm(int x)
  {
    prologue();
    if (x > 42)
      x = downsize(x);
    x = crunch(x);
    epilogue();
    return x;
  }

  void prologue()
  {}

  int downsize(int x)
  { return x % 42; }

  int crunch(int x)
  { return -x; }

  void epilogue()
  {}
};

struct Derived : Base
{
  int downsize(int x)
  {
    while (x > 42) x /= 2;
    return x;
  }

  void epilogue()
  { std::cout << "We're done!\n"; }
};

int main()
{
  Derived d;
  std::cout << d.algorithm(420);
}
Run Code Online (Sandbox Code Playgroud)

这输出:

0

[实例]

由于C++类型系统的静态特性,d.algorithm调用所有函数来自Base.Derived未调用尝试的覆盖.

使用CRTP时会发生这种情况:

template <class Self>
struct Base
{
  Self& self() { return static_cast<Self&>(*this); }

  int algorithm(int x)
  {
    self().prologue();
    if (x > 42)
      x = self().downsize(x);
    x = self().crunch(x);
    self().epilogue();
    return x;
  }

  void prologue()
  {}

  int downsize(int x)
  { return x % 42; }

  int crunch(int x)
  { return -x; }

  void epilogue()
  {}
};

struct Derived : Base<Derived>
{
  int downsize(int x)
  {
    while (x > 42) x /= 2;
    return x;
  }

  void epilogue()
  { std::cout << "We're done!\n"; }
};

int main()
{
  Derived d;
  std::cout << d.algorithm(420);
}
Run Code Online (Sandbox Code Playgroud)

输出:

我们完成了!
-26

[实例]

这样,实现Base就会实际调用,Derived只要Derived提供"覆盖".

这甚至可以在您的原始代码中看到:如果Base不是CRTP类,则其调用static_sub_func永远不会解析为Derived::static_sub_func.


至于CRTP相对于其他方法的优势是什么:

  • CRTP与virtual功能:

    CRTP是一个编译时构造,意味着没有关联的运行时开销.通过基类引用(通常)调用虚函数需要通过指向函数的指针进行调用,从而导致间接成本并阻止内联.

  • CRTP与简单实现以下内容Derived:

    基类代码重用.

当然,CRTP是纯粹的编译时构造.要实现它允许的编译时多态性,必须使用编译时多态构造:templates.有两种方法可以做到这一点:

template <class T>
int foo(Base<T> &actor)
{
  return actor.algorithm(314);
}

template <class T>
int bar(T &actor)
{
  return actor.algorithm(314);
}
Run Code Online (Sandbox Code Playgroud)

前者更接近运行时多态性并提供更好的类型安全性,后者更基于鸭子类型.