CRTP and multilevel inheritance
我的一个朋友问我"如何使用CRTP来取代多层次遗传中的多态性"。更准确地说,在这种情况下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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并不是多态性的替代品,但我们对两种模式都可以使用的情况很感兴趣。(为了这个问题,我们对每种模式的利弊都不感兴趣。)
以前有人问过这个问题,但结果证明,作者希望实现命名参数惯用法,他自己的答案更多地关注这个问题,而不是CRTP。另一方面,最有投票权的答案似乎只是一个派生类方法,它在基类中调用了它的同名项。
我想出了一个答案(张贴在下面),它有很多样板代码,我想知道是否有更简单的选择。
(1)层次结构中最顶层的类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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. }; |
备注:不幸的是,当使用
1 2 | protected: ~A() {} |
但是,对于调用构造函数与调用析构函数不匹配的情况(这可能通过
当允许实例化
(2)层次结构中间的类(或上面段落中解释的最上面的类)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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() "; } // 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(); } }; |
构造函数和析构函数是公共的,
1 2 | B<> b; b.foo(); |
注意,
测试
(3)最后,层次结构的底部如下:
1 2 3 4 5 6 7 8 9 10 | class C : public B<C> { public: void foo() const { std::cout <<"C::foo() "; } }; |
或者,如果继承的实现(
注意,
(4)也见:
如何在使用CRTP时避免错误?
注:这不是"最终覆盖"问题的具体解决方案,而是一般的CRTP多层次继承问题(因为我在任何地方都没有找到解决方法,我认为我的发现会很有用)。
编辑:我在这里发布了最终覆盖问题的解决方案
我最近了解到crtp及其作为运行时多态性静态替换的潜力。在搜索了一段时间后,我发现crtp是否可以作为一个类似的多态性"插入"替代品,这样你就可以使用多层次的继承等等,我不得不说,我很惊讶地发现,在没有样板的情况下,我找不到一个合适的通用解决方案,样板可以无限扩展。毕竟,考虑到CRTP的所有性能优势,为什么不尝试让它成为多态性的替代品呢?接下来进行了一些调查,我想说的是:
问题:
经典的CRTP模式在CRTP接口和实现类之间创建了一个可访问性的"循环"。(crtp接口类通过自身对模板参数类型的静态强制转换访问"base"实现类,实现类从crtp接口类继承公共接口。)创建具体实现时,将关闭循环,使从具体imp继承非常困难。元素作用类,使得从它派生的东西也以多态的方式表现。
经典的CRTP单级继承
解决方案:
将模式分为三个概念:
- "抽象接口类",即CRTP接口。
- "可继承的实现类",可由其他可继承的实现类无限期继承。
- "具体类",它将抽象接口与所需的可继承实现类结合在一起,并关闭循环。
下面是一个图表来帮助说明:
CRTP的多级继承
技巧是将具体的实现类作为模板参数一直传递到所有可继承实现类中的抽象接口类中。
通过这种方法,您可以:
它完美地反映了虚拟/运行时多态性。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | #include <iostream> template <class Top> struct CrtpInterface { void foo() { std::cout <<"Calling CrtpInterface::foo() "; fooImpl(); } void foo2() { std::cout <<"Calling CrtpInterface::foo2() "; fooImpl2(); } void foo3() { std::cout <<"Calling CrtpInterface::foo3() "; fooImpl3(); } void foo4() { std::cout <<"Calling CrtpInterface::foo4() "; 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() "; } void fooImpl2() { std::cout <<"Default::fooImpl2() "; std::cout <<"Calling foo() from interface "; impl::foo(); } void fooImpl3() { std::cout <<"Default::fooImpl3() "; std::cout <<"Calling highest level fooImpl2() from interface "; impl::fooImpl2(); } void fooImpl4() { std::cout <<"Default::fooImpl4() "; std::cout <<"Calling highest level fooImpl3() from interface "; impl::fooImpl3(); } }; template<class Top> class AImpl : public DefaultImpl<Top> { using impl = CrtpInterface<Top>; friend impl; void fooImpl() { std::cout <<"A::fooImpl() "; } }; 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 <<" "; } }; struct B : BImpl { }; 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 <<" "; } }; 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() "; } }; struct D : DImpl<D> { }; int main() { std::cout <<"### A ### "; A a; a.foo(); a.foo2(); a.foo3(); a.foo4(); std::cout <<"### B ### "; B b; b.foo(); b.foo2(); b.foo3(); b.foo4(); std::cout <<"### C ### "; C c; c.foo(); c.foo2(); c.foo3(); c.foo4(); std::cout <<"### D ### "; D d; d.foo(); d.foo2(); d.foo3(); d.foo4(); } |
哪些印刷品:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | ### 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() |
使用这种方法和"variant-style"包装器(使用一些sechsy variandic模板和宏构建,也许稍后我会发布它),它就像一个指向虚拟抽象基类的指针,我能够有效地创建继承自同一接口的crtp类的向量。
我将性能与like虚拟类的like向量进行了比较,所有这些都基于等效的虚拟接口,我发现使用这种方法,根据场景的不同,我可以将性能提高8倍!这是非常令人鼓舞的,因为生成功能多态的CRTP类层次结构所需的开销相对较少!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | template<class Derived> struct A { void foo() { static_cast<Derived*>(this)->foo(); } }; template<class Derived> struct B: A <Derived> { void foo() { // do something } }; struct C: B <C> { void foo(); // can be either present or absent }; |
如果缺少c中的foo(),将调用b中的foo()。否则,B中的将被覆盖。
当我意识到我最初的答案实际上并没有处理手头上的最后一个覆盖问题后,我想我应该再加一个。我想以与我以前的回答类似的方式提出一个"最终覆盖"解决方案。
问题:
crtp接口类总是通过静态强制转换重定向到最高派生类。这与"final"函数的概念是不一致的:如果所需的"final"函数没有在最高派生类上实现,并且被更高的类"重写"(因为您不能给函数"final"属性,除非它是虚拟的,这是我们在crtp中试图避免的),crtp接口将重定向到所需的"final"。除了"超控"功能。
解决方案:
将接口分为三个概念:
- 不带任何重定向函数的抽象接口类,它继承:
- 一个抽象重定向类,其重定向函数重定向到最高派生类,除非一个或多个重定向函数被以下对象覆盖:
- 一个具体的"重定向覆盖"类,它用一个实现覆盖重定向函数。
在实例化具体实现类时,我们不将具体实现类作为模板参数通过所有"可继承实现类"传入接口,而是将接口将从中继承的重定向类作为模板参数传递。
当我们想要使一个函数成为"final"时,我们只需创建一个从抽象重定向类继承的"redirect override class",并重写我们想要使其成为final的重定向函数。然后,我们将这个新的"重定向覆盖类"作为参数传递给所有可继承的实现类。
采用这种方法:
这听起来很复杂,所以这里有一个流程图,我试图让事情更容易理解:
dimpl和eimpl具有最终函数,当dimpl或eimpl从以下对象继承时,不能重写这些函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 | #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() "; fooImpl(); } void foo2() { std::cout <<"Calling Interface::foo2() "; fooImpl2(); } void foo3() { std::cout <<"Calling Interface::foo3() "; fooImpl3(); } void foo4() { std::cout <<"Calling Interface::foo4() "; 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() "; } void fooImpl2() { std::cout <<"Default::fooImpl2() "; std::cout <<"Calling foo() from interface "; impl::foo(); } void fooImpl3() { std::cout <<"Default::fooImpl3() "; std::cout <<"Calling highest level fooImpl2() from interface "; impl::fooImpl2(); } void fooImpl4() { std::cout <<"Default::fooImpl4() "; std::cout <<"Calling highest level fooImpl3() from interface "; impl::fooImpl3(); } }; template<class V> struct AImpl : public DefaultImpl<V> { template<class> friend struct Redirect; protected: void fooImpl() { std::cout <<"A::fooImpl() "; } }; 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 <<" "; } }; struct B : BImpl { }; 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 <<" "; } }; 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() "; } }; // 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() "; } void fooImpl3() { std::cout <<"E::fooImpl3() "; } // This will never be called, because fooImpl4 is final in DImpl void fooImpl4() { std::cout <<"E::fooImpl4(): this should never be printed "; } }; 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 "; } 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() "; // This will only call DFinal::fooImpl4(); std::cout <<"Calling fooImpl4() polymorphically. (Should not print FImplFinal::fooImpl4() or EImpl::fooImpl4()) "; impl::fooImpl4(); } void fooImpl3() { std::cout <<"FImpl::fooImpl3(), i =" << i << ' '; } }; struct F : FImpl<F> { }; int main() { std::cout <<"### A ### "; A a; a.foo(); a.foo2(); a.foo3(); a.foo4(); std::cout <<"### B ### "; B b; b.foo(); b.foo2(); b.foo3(); b.foo4(); std::cout <<"### C ### "; C c; c.foo(); c.foo2(); c.foo3(); c.foo4(); std::cout <<"### D ### "; D d; d.foo(); d.foo2(); d.foo3(); d.foo4(); std::cout <<"### E ### "; E e; e.foo(); e.foo2(); e.foo3(); e.foo4(); std::cout <<"### F ### "; F f; f.foo(); f.foo2(); f.foo3(); f.foo4(); } |
代码打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | ### 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() |
多层次继承不是问题,但是CRTP是如何创建多态性的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | template<typename Derived> struct Base { void f() { /* Basic case */ } //"Pure virtual" method void pure() { static_cast<Derived*>(this)->pure(); } }; struct Overriding: Base<Overriding> { void f() { /* Special case */ } // This method must exists to prevent endless recursion in Base::f void pure() { /* ... */ } }; struct NonOverriding: Base<NonOverriding> { void pure() { /* ... */ } }; template<typename Derived> void f(const Base<Derived>& base) { base.f(); // Base::f base.pure(); // Base::pure, which eventually calls Derived::pure // Derived::f if an overriding method exists. // Base::f otherwise. static_cast<const Derived&>(base).f(); } |
还可以引入
1 2 3 4 5 6 | template<typename Derived> struct Base { Derived& derived() { return *static_cast<Derived*>(this); } const Derived& derived() const { return *static_cast<const Derived*>(this); } }; |
在这个线程中有很多事情正在进行,但我发现它们没有用处,所以我在这里分享我自己解决这个问题的方法。
CRTP主要是一种代码缩减模式。为了正确地工作,有必要在继承层次的每一个层次上,都能够从下面的层次调用所有函数——就像在通常的动态继承中一样。
但是,在crtp中,每个阶段都必须进一步了解当前从中派生的最终类型,因为最终这是crtp的全部要点——调用始终应用于当前(静态)最终类型的函数。
可以通过在静态继承层次结构的每个级别添加一个间接层来实现这一点,如下例所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | template<typename derived_t> struct level0_impl { auto const& derived() const { return static_cast<derived_t const&>(*this); } }; struct level0 : public level0_impl<level0> { using level0_impl<level0>::level0_impl; }; template<typename derived_t> struct level1_impl : level0_impl<derived_t> { auto only_for_level1_and_derived() const { return derived().foo; }; auto do_something() const { std::cout<<"hi"<<std::endl; } }; struct level1 : public level1_impl<level1> { using level1_impl<level1>::level1_impl; }; template<typename derived_t> struct level2_impl : public level1_impl<derived_t> { auto only_for_level2_and_derived() const { return derived().bar; }; }; struct level2 : public level2_impl<level2> { using level2_impl<level2>::level2_impl; }; // ... and so on ... |
可以将其与最终类型一起使用,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <iostream> struct final : public level2_impl<final> { int foo = 1; double bar = 2.0; }; int main() { final f{}; std::cout<< f.only_for_level1_and_derived() <<std::endl; //prints variable foo = 1 std::cout<< f.only_for_level2_and_derived() <<std::endl; //prints variable bar = 2.0 } |
或者,只需删除
1 | level1{}.do_something(); //prints"hi" |
这是一件好事,它显然不适用于此线程中的其他方法,例如
1 2 3 4 5 | template<typename T> class A { auto& derived() {return static_cast<T&>(*this);} }; template<typename T> class B : A<B<T> > {}; template<typename T> class C : B<C> {}; //here derived() in the base class does //not return C, but B<C> -- which is //not what one usually wants in CRTP |
这里有一个可能的实现,它可以减少类内的样板代码(但不能减少辅助代码的总量)。
这个解决方案的思想是使用sfinae和重载来选择impl函数。
(i)甲级
1 2 3 4 5 | template <typename T> class A { void foo() const { static_cast<const T*>(this)->foo( Type2Type<T> ); } } |
其中typetotype是模板结构
1 2 3 | template< typename T > struct Type2Type { typedef T OriginalType } |
这对于帮助编译器选择foo()impl很有用。超载。
(一)乙类
1 2 3 4 5 6 7 8 9 10 11 | template <typename T = void> class B : public A<B<T>> { void foo(...) { std::cout <<"B::foo() "; } void foo( Type2Type< std::enable_if< is_base_of<T,B>::value == true, B>::type> ) { static_cast<const T*>(this)->foo( Type2Type<T> ); } } |
这里,如果层次结构的底部由C给出,则不需要typetotype参数。
(ii)C类
1 2 3 4 5 6 | class C : public B<C> { void foo(...) { std::cout <<"C::foo() "; } } |
我知道std::is_base_of returns true if t==b。这里,我们使用我们自己的is_base_of the returns false_type,当两个模板参数相同时。类似的东西
1 2 3 4 5 6 | template<class B, class D> struct is_base_of : public is_convertible<D *, B *> {}; template<class B, class B> struct is_base_of<B, B> : public false_type {}; template<class D> struct is_base_of<void, D> : public false_type {}; |
结论:如果std::enable_失败,那么foo()的变量版本将是唯一可用的版本(因为sfinae),编译器将实现foo的B版本。但是,如果启用"if"没有失败,编译器将选择第二个版本(因为当编译器试图找出重载函数之间的最佳匹配时,variadic是最后一个选项)。