奇异递归模版模式不“奇异”
同学们是否有听说过奇异递归模版模式(CRTP)?听说过的同学大致也知道其代码编写格式是怎么样的?但是,同学们是否有弄清楚过其是怎么达到这种效果的?接下来就简单聊聊!
一、奇异递归模板模式
下面是奇异递归模板模式的一般编写格式:
1 template<typename T> 2 class Base 3 { 4 public: 5 Base() 6 { 7 std::cout << "Base::Base<" << typeid(T).name() << "> ctor\n"; 8 } 9 ~Base() 10 { 11 std::cout << "~Base::Base<" << typeid(T).name() << "> dtor\n"; 12 } 13 void Interface() 14 { 15 std::cout << "Base::Interface()\n"; 16 static_cast<T*>(this)->Implement1(); 17 } 18 }; 19 20 21 class D : public Base<D> 22 { 23 public: 24 D() 25 { 26 std::cout << "D::D<" << typeid(this).name() << "> ctor\n"; 27 } 28 ~D() 29 { 30 std::cout << "~D::D<" << typeid(this).name() << "> dtor\n"; 31 } 32 33 void Implement() 34 { 35 std::cout << "D::Implement()\n"; 36 } 37 };
有一个基类模板,子类继承该基类时,使用子类实例化基类。起到调用基类接口实际上动态调用子类方法的作用,又被称之为“静态多态”(和重载不同)。调用方法如下:
1 int main() 2 { 3 D b; 4 b.Interface(); 5 return 0; 6 }
运行结果:
要想弄清楚奇异递归模板模式如何达到这种效果,得先弄清楚两个基础问题:
- 类模板或函数模板什么时候被编译?
- std::static_cast能否向上转换?
二、类模板或函数模板什么时候被编译?
回答类模板或函数模板什么时候被编译前,先问自己:函数模板和模板函数,类模板和模板类的区别是什么。思考下... ...。其实类模板或函数模板,就是带有模板类型参数的类或函数,比如上面的奇异递归模板就是一个类模板;模板类或函数,就是实例化模板的类或函数,比如子类继承基类就是模板类,也叫做模板实例化。我们编写的类模板或函数模板,在没有实例化前,是不会被编译器编译。怎么验证呢?很简单,没有实例化上面的奇异递归模板时,我随便写过不存在的调用函数,编译不会出现错误。如下类模板:
1 template<typename T> 2 class Base 3 { 4 public: 5 Base() 6 { 7 std::cout << "Base::Base<" << typeid(T).name() << "> ctor\n"; 8 } 9 ~Base() 10 { 11 std::cout << "~Base::Base<" << typeid(T).name() << "> dtor\n"; 12 } 13 void Interface() 14 { 15 std::cout << "Base::Interface()\n"; 16 static_cast<T*>(this)->Implement1(); // 不存在的函数 17 auto value = T::value; // 不存在的值 18 } 19 };
如果实例化类模板,那么就会编译报错:
预处理后的预处理文件还是没有对类模板进行类型推导:
所以,通过上面的验证,类模板或函数模板必须要实例化,然后编译器才会在编译阶段的时候进行类型推导并编译出obj文件。
三、std::static_cast能否向上转换?
先按照老规则,官方文档链接:static_cast conversion - cppreference.com。
第9种情况说明了std::static_cast可以向上转换,但也强调,这种转换并不进行检查以确保转换后对象的成员是否存在。下面可以通过代码验证:
1 class B { 2 public: 3 B() 4 : _b(1) 5 { 6 std::cout << "B::B() ctor\n"; 7 } 8 virtual ~B() 9 { 10 std::cout << "B::~B() dctor\n"; 11 } 12 13 private: 14 int _b; 15 }; 16 17 class D : public B 18 { 19 public: 20 D() 21 : _d(2) 22 { 23 std::cout << "D::D() ctor\n"; 24 } 25 ~D() 26 { 27 std::cout << "D::~D() ctor\n"; 28 } 29 void Func() 30 { 31 //this->_d = 2; 32 std::cout << "D::Func() " << _d << std::endl; 33 } 34 private: 35 int _d; 36 }; 37 38 int main() 39 { 40 auto b = new B(); 41 auto d = static_cast<D*>(b); 42 d->Func(); 43 return 0; 44 }
虽然转换后的子类可以正常调用子类函数,但是子类成员变量是随机值,这个也验证了官方文档中第9条转换规则中强调的问题。关于奇异递归模板模式的作用和使用问题,在实际项目中没有使用过,但是在boost等一些开源库中有涉及到它,感兴趣的同学可以访问下面的参考链接地址,进一步了解和学习奇异递归模板模式。
参考:The Curiously Recurring Template Pattern (CRTP) - Fluent C++ (fluentcpp.com)