我试图绕过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();
};
答案 0 :(得分:9)
事情是将CRTP描述为&#34;静态多态性&#34;关于CRPT实际使用的内容,并不是真正有用或准确。多态性实际上只是拥有完成相同接口或契约的不同类型;这些不同类型如何实现该接口与多态是正交的。动态多态性如下所示:
void foo(Animal& a) { a.make_sound(); } // could bark, meow, etc
Animal
是提供虚拟make_sound
方法的基类,Dog
,Cat
等覆盖。这是静态多态:
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
由于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
这样,只要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);
不是类型,第二个不是多态的。
但是,假设您有两个派生类Base
和Derived1
。然后,你可以做的是让Derived2
本身成为一个模板。
func
然后可以使用从template <typename T>
void func(Base<T>& x);
派生的任何类型调用它,并且它将使用传递的任何参数的静态类型来决定调用哪个函数。
这只是CRTP的一个用途,如果我猜测我会说不太常见的那个。您也可以在另一个答案中使用Nir Friedman建议,这与静态多态无关。
两种用途都得到了很好的讨论here