关于CRTP静态多态性的困惑

时间:2017-05-06 14:20:09

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

据我所知,这有助于我在派生类中使用不同的implementation()变体,有点像编译时虚函数。但是,我的困惑是我认为我不能拥有像

这样的功能
void func(Base x){
    x.implementation();
}
像普通继承和虚函数一样,由于Base被模板化,但是我必须指定

func(Derived x)

或使用

template<class T> 
func(T x)

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

struct Base
{
    void implementation();

struct Derived : public Base
{
    void implementation();
    static void static_sub_func();
};

3 个答案:

答案 0 :(得分:9)

事情是将CRTP描述为&#34;静态多态性&#34;关于CRPT实际使用的内容,并不是真正有用或准确。多态性实际上只是拥有完成相同接口或契约的不同类型;这些不同类型如何实现该接口与多态是正交的。动态多态性如下所示:

void foo(Animal& a) { a.make_sound();  } //  could bark, meow, etc

Animal是提供虚拟make_sound方法的基类,DogCat等覆盖。这是静态多态:

template <class T>
void foo(T& a) { a.make_sound(); }

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

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

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

现在任何定义加法运算符的类也可以被赋予double方法:

class Matrix : MakeDouble<Matrix> ...

Matrix m;
auto m2 = m.double();

CRTP就是帮助实现,而不是接口。因此,不要太常谈论它经常被称为静态多态性&#34;的事实。如果你想要了解CRTP可用的真实规范示例,请考虑Andrei Alexandrescu的Modern C ++设计的第1章。但是,慢慢来: - )。

答案 1 :(得分: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);
}

输出:

  

0

[Live example]

由于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);
}

输出:

  

我们完成了!
  -26

[Live example]

这样,只要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);
}

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

答案 2 :(得分:0)

你是对的

class YourCompany(models.Model):
    _inherit = "res.company"

    company_commission =  fields.Float( default =15.00)
    commission_ids = fields.One2many( 'sale.order', 'company_id', 
        string="Commission_ids")

class SaleOrder(models.Model):
    _inherit = "sale.order"
    company_id = fields.Many2one('res.company', string='company_id', 
          required=True)
    company_commission = 
                fields.Float(related='company_id.company_commission', 
         string="Comm", store = True)

void func(Base x);

为您提供静态多态性。第一个不编译,因为void func(Derived x); 不是类型,第二个不是多态的。

但是,假设您有两个派生类BaseDerived1。然后,你可以做的是让Derived2本身成为一个模板。

func

然后可以使用从template <typename T> void func(Base<T>& x); 派生的任何类型调用它,并且它将使用传递的任何参数的静态类型来决定调用哪个函数。

这只是CRTP的一个用途,如果我猜测我会说不太常见的那个。您也可以在另一个答案中使用Nir Friedman建议,这与静态多态无关。

两种用途都得到了很好的讨论here