2017-05-06 51 views
8

我正试图围绕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(); 
}; 

我明白,这可以帮助我有不同的实现()的派生类变种,有点像一个编译时虚函数。然而,我的困惑是,我想我不能有像

void func(Base x){ 
    x.implementation(); 
} 

功能我会与正常的继承和虚函数,由于基数正在模板,但我必须要么指定

func(Derived x) 

或使用

template<class T> 
func(T x) 

那么,是什么CRTP实际购买我在这方面,而不是简单地遮蔽/实施中派生:: Base的简单方法?

struct Base 
{ 
    void implementation(); 

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

当你把由值的'Base'例如,你从[对象切片]遭受(https://en.wikipedia.org/wiki/Object_slicing)。如果您想要多态行为(无论使用静态还是动态多态),请通过引用或指针传递它。 –

+0

不适用于我,我得到变量或字段'func'声明为void void func(Base&x)... – user32849

+0

@ user32849'Base'是一个模板。你不能只使用'Base&',你必须提供一个模板参数:'Base &'。这意味着'func'也必须是一个模板。 – Angew

回答

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

此输出:

[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是纯粹的编译时构造。为了达到它允许的编译时多态性,你必须使用编译时多态结构:模板。有两种方法可以做到这一点:

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

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

前者对应更紧密地运行时多态性,并提供更好的类型安全,后者是基于更鸭打字。

+0

这显然是CRTP的一个很好的使用,但是我的问题和CRTP的描述一般是“静态多态性”,CRTP与多态性没有任何直接关系。这是一个帮助实施的工具。 –

+0

@NirFriedman它确实与多态性有关,但是*编译时*多态性。这只能通过模板来实现。我会在答案中加上一点。 – Angew

+0

我明白编译时多态性是什么。是的,你的代码中有一个模板类。但CRTP并不是纯粹根据其接口使用类型的函数,而是多态性。 Base仅仅是一个帮助实现Derived或类似类的工具。如果它是关于实现的帮助,那么关于多态只是关于接口,并不是真的。 –

6

问题在于CRTP作为“静态多态性”的描述对于实际使用的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是所有关于实施帮助,而不是接口。所以不要太在意它通常被称为“静态多态性”的事实。如果您想要了解可以使用CRTP的真正规范示例,请考虑Andrei Alexandrescu的Modern C++设计的第1章。虽然,慢慢来:-)。

+0

CRTP有多种用途。这是其中之一,但提问者所谈论的也是一个有效的方法。 –

0

你是正确的,无论

void func(Base x); 

void func(Derived x); 

给你静态的多态性。第一个不编译,因为Base不是一个类型,第二个不是多态。

但是,假设您有两个派生类,Derived1Derived2。那么,你可以做的是使func本身成为一个模板。

template <typename T> 
void func(Base<T>& x); 

然后可以与任何类型从Base派生调用,它会使用任何的参数传递给决定哪些函数来调用静态类型。


这只是CRTP的用途之一,如果我猜测我会说不太常见的一种。你也可以使用它作为Nir Friedman在另一个答案中提出,这与静态多态性没有任何关系。

两种用途进行了讨论很好here