我的一个朋友问我“如何使用CRTP来取代多级遗传中的多态性”。更确切地说,在这种情况下:
struct A {
void bar() {
// do something and then call foo (possibly) in the derived class:
foo();
}
// possibly non pure virtual
virtual void foo() const = 0;
}
struct B : A {
void foo() const override { /* do something */ }
}
struct C : B {
// possibly absent to not override B::foo().
void foo() const final { /* do something else */ }
}我和我的朋友都知道CRTP并不是多态的替代物,但是我们对这两种模式都可以使用的情况很感兴趣。(为了这个问题,我们对每种模式的利弊都不感兴趣。)
发布于 2013-08-11 17:02:51
(1)层次结构中最顶层的类如下所示:
template <typename T>
class A {
public:
void bar() const {
// do something and then call foo (possibly) in the derived class:
foo();
}
void foo() const {
static_cast<const T*>(this)->foo();
}
protected:
~A() = default;
// Constructors should be protected as well.
};A<T>::foo()的行为类似于纯虚拟方法,因为它没有“默认实现”,调用指向派生类。但是,这并不妨碍将A<T>实例化为非基类。为了获得这种行为,A<T>::~A()是protected。
备注:不幸的是,当使用GCC虫时,= default;会使特殊的成员函数公开。在这种情况下,应该使用
protected:
~A() {}但是,对于对构造函数的调用与对析构函数的调用不匹配的情况(这可能通过operator new发生),保护析构函数是不够的。因此,最好也保护所有构造函数(包括复制和移动构造函数)。
当应该允许A<T>的实例化并且A<T>::foo()的行为应该像一个非纯的虚拟方法时,那么A应该类似于下面的模板类B。
(2)层次结构中间的类(或最上面的类,如上面一段所述)如下所示:
template <typename T = void>
class B : public A<B<T>> { // no inherinace if this is the topmost class
public:
// Constructors and destructor
// boilerplate code :-(
void foo() const {
foo_impl(std::is_same<T, void>{});
}
private:
void foo_impl(std::true_type) const {
std::cout << "B::foo()\n";
}
// boilerplate code :-(
void foo_impl(std::false_type) const {
if (&B::foo == &T::foo)
foo_impl(std::true_type{});
else
static_cast<const T*>(this)->foo();
}
};构造函数和析构函数是公共的,T默认为void。这允许B<>类型的对象在层次结构中派生最多,因此这是合法的:
B<> b;
b.foo();还请注意,B<T>::foo()的行为是一个非纯虚拟方法,也就是说,如果B<T>是最派生的类(或者更准确地说,如果T是void),那么b.foo();调用“foo()的默认实现”(它输出B::foo())。如果T不是void,则调用指向派生类。这是通过标记分派来完成的。
为了避免无限递归调用,需要测试&B::foo == &T::foo。实际上,如果派生类T没有重新实现foo(),则调用static_cast<const T*>(this)->foo();将解析为再次调用B::foo_impl(std::false_type)的B::foo()。此外,这个测试可以在编译时解析,代码要么是if (true),要么是if (false),优化器可以完全删除测试(例如GCC和-O3)。
(3)最后,层次结构的底部如下所示:
class C : public B<C> {
public:
void foo() const {
std::cout << "C::foo()\n";
}
};或者,如果继承的实现( C::foo() )足够充分,则可以完全删除B<C>::foo()。
注意,C::foo()类似于最后一个方法,因为调用它不会将调用重定向到派生类(如果有的话)。(为了使其非最终,应该使用像B这样的模板类。)
(4)也见:
发布于 2016-08-30 20:37:59
注意:这不是针对“最终覆盖”问题的具体解决方案,而是对CRTP多层继承问题的一般解决方案(因为我在任何地方都没有找到如何这样做的答案,我认为我的发现将是有用的)。
编辑:我已经发布了最终覆盖问题这里的解决方案
最近,我了解到CRTP及其作为运行时多态性的静态替代的潜力。在搜索了一段时间之后,看看CRTP是否可以作为类似于“插入”的多态替换,这样您就可以使用多层继承等等,我不得不说,我很惊讶在任何地方都找不到合适的通用解决方案,如果没有样板,它可以无限期地扩展。毕竟,考虑到它的所有性能好处,为什么不尝试将CRTP作为多态的替代物呢?随后进行了一些调查,下面是我得出的结论:
问题:
典型的CRTP模式在CRTP接口和实现类之间创建了一个可访问性的“循环”。( CRTP接口类通过自身对模板参数类型的静态转换访问“基类”实现类,实现类从CRTP接口类继承公共接口。)当您创建一个具体的实现时,您将关闭循环,这使得很难从具体的实现类继承,因此从它派生的东西也以多态的方式运行。
解决方案:
将模式分为三个概念:
这里有一个图表来帮助说明:
诀窍是将具体的实现类作为模板参数通过所有可继承的实现类一直传递到抽象接口类中。
通过这种方法,您可以:
它完美地反映了虚拟/运行时多态性。
示例代码:
#include <iostream>
template <class Top>
struct CrtpInterface
{
void foo()
{
std::cout << "Calling CrtpInterface::foo()\n";
fooImpl();
}
void foo2()
{
std::cout << "Calling CrtpInterface::foo2()\n";
fooImpl2();
}
void foo3()
{
std::cout << "Calling CrtpInterface::foo3()\n";
fooImpl3();
}
void foo4()
{
std::cout << "Calling CrtpInterface::foo4()\n";
fooImpl4();
}
// The "pure virtual functions"
protected:
inline void fooImpl()
{
top().fooImpl();
}
inline void fooImpl2()
{
top().fooImpl2();
}
inline void fooImpl3()
{
top().fooImpl3();
}
inline void fooImpl4()
{
top().fooImpl4();
}
inline Top& top()
{
return static_cast<Top&>(*this);
}
};
template<class Top>
class DefaultImpl : public CrtpInterface<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
void fooImpl()
{
std::cout << "Default::fooImpl()\n";
}
void fooImpl2()
{
std::cout << "Default::fooImpl2()\n";
std::cout << "Calling foo() from interface\n";
impl::foo();
}
void fooImpl3()
{
std::cout << "Default::fooImpl3()\n";
std::cout << "Calling highest level fooImpl2() from interface\n";
impl::fooImpl2();
}
void fooImpl4()
{
std::cout << "Default::fooImpl4()\n";
std::cout << "Calling highest level fooImpl3() from interface\n";
impl::fooImpl3();
}
};
template<class Top>
class AImpl : public DefaultImpl<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
void fooImpl()
{
std::cout << "A::fooImpl()\n";
}
};
struct A : AImpl<A>
{
};
template<class Top>
class BImpl : public AImpl<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
protected:
BImpl()
: i{1}
{
}
private:
int i;
void fooImpl2()
{
std::cout << "B::fooImpl2(): " << i << "\n";
}
};
struct B : BImpl<B>
{
};
template<class Top>
class CImpl : public BImpl<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
protected:
CImpl(int x = 2)
: i{x}
{
}
private:
int i;
void fooImpl3()
{
std::cout << "C::fooImpl3(): " << i << "\n";
}
};
struct C : CImpl<C>
{
C(int i = 9)
: CImpl(i)
{
}
};
template<class Top>
class DImpl : public CImpl<Top>
{
using impl = CrtpInterface<Top>;
friend impl;
void fooImpl4()
{
std::cout << "D::fooImpl4()\n";
}
};
struct D : DImpl<D>
{
};
int main()
{
std::cout << "### A ###\n";
A a;
a.foo();
a.foo2();
a.foo3();
a.foo4();
std::cout << "### B ###\n";
B b;
b.foo();
b.foo2();
b.foo3();
b.foo4();
std::cout << "### C ###\n";
C c;
c.foo();
c.foo2();
c.foo3();
c.foo4();
std::cout << "### D ###\n";
D d;
d.foo();
d.foo2();
d.foo3();
d.foo4();
}其中的指纹:
### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
D::fooImpl4()使用这种方法和一个“变体式”包装器(使用一些sechsy的可变模板和宏构建,也许我稍后会发布),它的作用就像指向虚拟抽象基类的指针一样,我能够有效地创建从同一个接口继承的CRTP类的向量。
我将性能与类似于同类虚拟类的向量进行比较,所有这些都基于等效的虚拟接口,并且我发现,使用这种方法,根据场景的不同,我可以实现高达8倍的性能提升!这非常令人鼓舞,因为生成功能多态CRTP类层次结构所需的开销相对较少!
发布于 2016-09-01 21:18:49
在意识到我的原始答案实际上并没有处理最终的覆盖问题之后,我想我应该补充一下。我想拿出一个“最终覆盖”的解决方案,以类似于我以前的答案。
问题:
CRTP接口类总是通过静态强制转换重定向到最高的派生类。这与“最终”函数的概念不一致:如果所需的“最终”函数没有在最高的派生类上实现,并且被更高的类“覆盖”(因为除非函数是虚拟的,否则不能赋予它“最终”属性,我们在CRTP中试图避免),则CRTP接口将不是重定向到所需的“最终”函数,而是重定向到“覆盖”。
解决方案:
将接口划分为三个概念:
在实例化具体实现类时,不通过所有“可继承实现类”将具体实现类作为模板参数传递到接口中,而是传递接口将继承的重定向类作为模板参数。
当我们想要使一个函数“最终”时,我们只需创建一个“重定向覆盖类”,它继承自抽象重定向类,并重写我们想要成为final的重定向函数。然后,我们将这个新的“重定向重写类”作为参数传递给所有可继承的实现类。
采用这一办法:
这一切听起来都很复杂,所以我做了一个流程图,试图让事情更容易理解:
DImpl和EImpl具有最终函数,当DImpl或EImpl继承自:

示例代码:
#include <iostream>
#include <type_traits>
template <class Top>
struct Redirect
{
protected:
// The "pure virtual functions"
inline void fooImpl()
{
top().fooImpl();
}
inline void fooImpl2()
{
top().fooImpl2();
}
inline void fooImpl3()
{
top().fooImpl3();
}
inline void fooImpl4()
{
top().fooImpl4();
}
inline Top& top()
{
// GCC doesn't allow static_cast<Top&>(*this)
// since Interface uses private inheritance
static_assert(std::is_base_of<Redirect, Top>::value, "Invalid Top class specified.");
return (Top&)(*this);
}
};
// Wraps R around the inner level of a template T, e.g:
// R := Redirect, T := X, then inject_type::type := Redirect<X>
// R := Redirect, T := A<B<C<X>>>, then inject_type::type := A<B<C<Redirect<X>>>>
template<template<class> class R, class T>
struct inject_type
{
using type = R<T>;
};
template<template<class> class R, class InnerFirst, class... InnerRest, template<class...> class Outer>
struct inject_type<R, Outer<InnerFirst, InnerRest...>>
{
using type = Outer<typename inject_type<R, InnerFirst>::type, InnerRest...>;
};
// We will be inheriting either Redirect<...> or something
// which derives from it (and overrides the functions).
// Use private inheritance, so that all polymorphic calls can
// only go through this class (which makes it impossible to
// subvert redirect overrides using future user code).
template <class V>
struct Interface : private inject_type<Redirect, V>::type
{
using impl = Interface;
void foo()
{
std::cout << "Calling Interface::foo()\n";
fooImpl();
}
void foo2()
{
std::cout << "Calling Interface::foo2()\n";
fooImpl2();
}
void foo3()
{
std::cout << "Calling Interface::foo3()\n";
fooImpl3();
}
void foo4()
{
std::cout << "Calling Interface::foo4()\n";
fooImpl4();
}
private:
using R = typename inject_type<::Redirect, V>::type;
protected:
using R::fooImpl;
using R::fooImpl2;
using R::fooImpl3;
using R::fooImpl4;
};
template<class V>
struct DefaultImpl : Interface<V>
{
template<class>
friend struct Redirect;
protected:
// Picking up typename impl from Interface, where all polymorphic calls must pass through
using impl = typename DefaultImpl::impl;
void fooImpl()
{
std::cout << "Default::fooImpl()\n";
}
void fooImpl2()
{
std::cout << "Default::fooImpl2()\n";
std::cout << "Calling foo() from interface\n";
impl::foo();
}
void fooImpl3()
{
std::cout << "Default::fooImpl3()\n";
std::cout << "Calling highest level fooImpl2() from interface\n";
impl::fooImpl2();
}
void fooImpl4()
{
std::cout << "Default::fooImpl4()\n";
std::cout << "Calling highest level fooImpl3() from interface\n";
impl::fooImpl3();
}
};
template<class V>
struct AImpl : public DefaultImpl<V>
{
template<class>
friend struct Redirect;
protected:
void fooImpl()
{
std::cout << "A::fooImpl()\n";
}
};
struct A : AImpl<A>
{
};
template<class V>
struct BImpl : public AImpl<V>
{
template<class>
friend struct Redirect;
protected:
BImpl()
: i{1}
{
}
private:
int i;
void fooImpl2()
{
std::cout << "B::fooImpl2(): " << i << "\n";
}
};
struct B : BImpl<B>
{
};
template<class V>
struct CImpl : public BImpl<V>
{
template<class>
friend struct Redirect;
protected:
CImpl(int x = 2)
: i{x}
{
}
private:
int i;
void fooImpl3()
{
std::cout << "C::fooImpl3(): " << i << "\n";
}
};
struct C : CImpl<C>
{
C(int i = 9)
: CImpl(i)
{
}
};
// Make D::fooImpl4 final
template<class V>
struct DImplFinal : public V
{
protected:
void fooImpl4()
{
std::cout << "DImplFinal::fooImpl4()\n";
}
};
// Wrapping V with DImplFinal overrides the redirecting functions
template<class V>
struct DImpl : CImpl<DImplFinal<V>>
{
};
struct D : DImpl<D>
{
};
template<class V>
struct EImpl : DImpl<V>
{
template<class>
friend struct Redirect;
protected:
void fooImpl()
{
std::cout << "E::fooImpl()\n";
}
void fooImpl3()
{
std::cout << "E::fooImpl3()\n";
}
// This will never be called, because fooImpl4 is final in DImpl
void fooImpl4()
{
std::cout << "E::fooImpl4(): this should never be printed\n";
}
};
struct E : EImpl<E>
{
};
// Make F::fooImpl3 final
template<class V, class Top>
struct FImplFinal : public V
{
protected:
// This is implemented in FImpl, so redirect
void fooImpl3()
{
top().fooImpl3();
}
// This will never be called, because fooImpl4 is final in DImpl
void fooImpl4()
{
std::cout << "FImplFinal::fooImpl4() this should never be printed\n";
}
inline Top& top()
{
// GCC won't do a static_cast directly :(
static_assert(std::is_base_of<FImplFinal, Top>::value, "Invalid Top class specified");
return (Top&)(*this);
}
};
// Wrapping V with FImplFinal overrides the redirecting functions, but only if they haven't been overridden already
template<class V>
struct FImpl : EImpl<FImplFinal<V, FImpl<V>>>
{
template<class>
friend struct Redirect;
template<class, class>
friend struct FImplFinal;
protected:
FImpl()
: i{99}
{
}
// Picking up typename impl from DefaultImpl
using impl = typename FImpl::impl;
private:
int i;
void fooImpl2()
{
std::cout << "F::fooImpl2()\n";
// This will only call DFinal::fooImpl4();
std::cout << "Calling fooImpl4() polymorphically. (Should not print FImplFinal::fooImpl4() or EImpl::fooImpl4())\n";
impl::fooImpl4();
}
void fooImpl3()
{
std::cout << "FImpl::fooImpl3(), i = " << i << '\n';
}
};
struct F : FImpl<F>
{
};
int main()
{
std::cout << "### A ###\n";
A a;
a.foo();
a.foo2();
a.foo3();
a.foo4();
std::cout << "### B ###\n";
B b;
b.foo();
b.foo2();
b.foo3();
b.foo4();
std::cout << "### C ###\n";
C c;
c.foo();
c.foo2();
c.foo3();
c.foo4();
std::cout << "### D ###\n";
D d;
d.foo();
d.foo2();
d.foo3();
d.foo4();
std::cout << "### E ###\n";
E e;
e.foo();
e.foo2();
e.foo3();
e.foo4();
std::cout << "### F ###\n";
F f;
f.foo();
f.foo2();
f.foo3();
f.foo4();
}代码打印:
### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### E ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
E::fooImpl3()
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### F ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
F::fooImpl2()
Attempting to call FFinal::fooImpl4() or E::fooImpl4()
DImplFinal::fooImpl4()
Calling CrtpInterface::foo3()
FImpl::fooImpl3(), i = 99
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()https://stackoverflow.com/questions/18174441
复制相似问题