Step By Step(C++模板基本技巧)
一、typename的另一种使用方式:
在此之前,我们了解到的有关该关键字的用途更多是针对模板参数的定义。而这里介绍的使用方式则有些不同,主要区别是typename的这种使用方式用于定义或提示编译器,其后修饰的标识符为模板参数中的类型标识符,而不是普通的静态类成员。见以下代码示例和关键性注释。
1 #include <stdio.h> 2 3 template<typename T> 4 class MyTestClass { 5 public: 6 //这里的typename是用于通知编译器,其后的MyType是模板T定义的内部类型,从这个代码示例 7 //中可以看出,不管是在函数参数、返回值还是定义成员变量定义,都要遵守这一语法规则。 8 MyTestClass(typename T::MyType p) : _p(p) { 9 } 10 typename T::MyType GetData() const { 11 return _p; 12 } 13 public: 14 typename T::MyType _p; 15 }; 16 //在该类中,由于后面的调用需要将其用作MyTestClass的模参,因此他必须typedef名字为MyType的 17 //内部类型,否则将会导致编译失败。 18 class TemplateParamClass { 19 public: 20 typedef const char* MyType; 21 }; 22 23 int main() { 24 MyTestClass<TemplateParamClass> t("Hello"); 25 printf("The data is %s.\n",t.GetData()); 26 return 0; 27 }
二、基于不同类型模参的模板类之间的赋值:
前面的博客已经提到过,编译器会将不同类型实例化后的模板类视为不同的类型,因此在这种不同的类型之间进行赋值,如果没有提供必要的方法是无法正常编译的,如:
1 template <typename T> 2 class TestClass { 3 ... ... 4 } 5 int main() { 6 TestClass<int> intClass; 7 TestClass<double> doubleClass = intClass; 8 }
上例中的代码是无法成功编译的,因为在C++编译器看来,TestClass<int>和TestClass<double>就是两个完全不同的类型,这样也就无法利用C++编译器自动生成的缺省赋值函数完全这一赋值功能。如何修改才可以达到这一效果呢,见如下代码示例和关键性注释。
1 #include <stdio.h> 2 3 template <typename T> 4 class TestClass { 5 public: 6 TestClass(T v) : _value(v) { 7 } 8 template<typename T2> 9 TestClass<T>& operator= (const TestClass<T2>& other) { 10 //这是编写重载赋值操作符的基本技巧,既不能进行自身对自身的赋值。 11 if ((void*)this == (void*)&other) 12 return *this; 13 //需要说明的是,由于other和this是通过不同类型实例化的模板类,因此这里不能 14 //像普通类那样直接调用other._value。编译器将他们视为不同的类型,所以也就不 15 //能直接访问其私有变量了。 16 _value = other.getValue(); 17 return *this; 18 } 19 T getValue() const { 20 return _value; 21 } 22 private: 23 T _value; 24 }; 25 26 int main() { 27 TestClass<int> intClass(5); 28 TestClass<double> doubleClass(6.4); 29 printf("The value before assignment is %f.\n",doubleClass.getValue()); 30 //此时的T为double,T2为int。 31 doubleClass = intClass; 32 printf("The value after assignment is %f.\n",doubleClass.getValue()); 33 return 0; 34 } 35 //The value before assignment is 6.400000. 36 //The value after assignment is 5.000000.
三、模板的模板参数:
所谓的模板的模板参数,是指模板的参数是另外一个模板类。如:
template<typename T>
class TestClass {
... ...
}
对于上面的模板类,其模参类型并没有任何限制,即可以为任意类型,如普通原始类型、类类型,模板类等。而对于要求模参必须要模板类类型的模板类而言,其模参必须是模板类,否则将无法通过编译。如:
template <template <typename T> class TemplateClass >
class TemplateTemplateClass {
... ...
}
对于上面的模板类,其模参必须是符合template<typename T> class签名的模板类,该类只有一个普通模参。见如下示例代码和关键性注释。
1 #include <stdio.h> 2 //该模板类的第二个模板必须是另外一个模板类。 3 template<typename T, template <typename T> class TemplateClass> 4 class TemplateTemplateClass { 5 public: 6 TemplateTemplateClass(const TemplateClass<T>& tempClass) 7 : _internalClass(tempClass) { 8 } 9 void doTest() { 10 _internalClass.doTest(); 11 } 12 private: 13 TemplateClass<T> _internalClass; 14 }; 15 16 template <typename T> 17 class MyTemplateClass { 18 public: 19 void doTest() { 20 printf("This is MyTemplateClass::doTest().\n"); 21 } 22 }; 23 24 int main() { 25 MyTemplateClass<int> tempClass; 26 TemplateTemplateClass<int,MyTemplateClass> tempTempClass(tempClass); 27 tempTempClass.doTest(); 28 return 0; 29 } 30 //This is MyTemplateClass::doTest().
四、零初始化:
对于成员变量而言,我们通常的做法是在类构造函数的初始化列表中完成这些成员变量的初始化工作,以免在使用这些变量时造成一些不必要的Bug。对于模板参数类型的成员变量我们应该如何处理呢?见如下示例代码和关键性注释:
1 #include <stdio.h> 2 3 template <typename T> 4 class TestClass { 5 public: 6 //1. 第一步是为模板类提供一个缺省构造函数,在该函数的初始化列表中显示的完成 7 //模参类型成员的变量的初始化。 8 //2. 在这个例子中,要求模板类型或者为原始类型,如int、double等。倘若是类类型 9 //则该类必须提供缺省构造函数,否则将导致编译失败。 10 //3. 对于原始类型,如int,在执行完_value()之后,其值将被初始化为0。 11 TestClass() : _value() { 12 } 13 private: 14 T _value; 15 }; 16 int main() { 17 TestClass<int> t; 18 return 0; 19 }
五、使用字符串作为模板函数的实参:
见如下代码:
1 template<typename T> 2 inline T const& max(T const& a, T const& b) { 3 return a < b ? b : a; 4 }
如果上述函数的模参类型为char*,那么函数在执行时将会直接使用指针的地址值直接进行比较,而不是按照一个常规方式,按照指针所指向的字符串数据进行lexical方式的比较。
对于上述函数还存在一个比较隐式的问题,即如果函数的参数不是char*而是字符数组的话,由于max的参数为T const&引用类型,因此在模板函数实例化时将会造成很多限制,见如下代码示例:
1 #include <stdio.h> 2 #include <typeinfo> 3 4 template <typename T> 5 void ref(T const& x) { 6 printf("x in ref(T const&): %s.\n",typeid(x).name()); 7 } 8 9 template <typename T> 10 void nonref(T x) { 11 printf("x in nonref(T): %s.\n",typeid(x).name()); 12 } 13 14 int main() { 15 ref("Hello"); 16 nonref("Hello"); 17 return 0; 18 } 19 //x in ref(T const&): char const [6]. 20 //x in nonref(T): char const *.
从上述输出结果可以看出,使用引用作为函数参数时,编译器会将模板实参推演为字符串数组,而非引用方式则不会。由于可以得出:
1 int main() { 2 //可以编译通过,因为他们都是char const [6] 3 max("apple","peach"); 4 //不可以编译通过,因为这两个函数参数的类型分别为char const[6]和char const [5] 5 //对于这种情况,我们可以考虑重载一个max函数,其函数参数为T,而不是T const& 6 max("apple","pear"); 7 return 0; 8 }