我正试着绕着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章.虽然,慢慢来:-).
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)
前者更接近运行时多态性并提供更好的类型安全性,后者更基于鸭子类型.