第20章 高级模板编程 模板的基本概念 模板是C++中实现泛型编程的核心机制,它允许我们编写与类型无关的代码,提高代码的重用性和灵活性。
什么是模板? 模板是一种参数化的代码蓝图,它可以根据不同的类型参数生成具体的代码。C++中的模板分为两种:
函数模板 :生成不同类型的函数类模板 :生成不同类型的类模板的优势 代码重用 :编写一次代码,适用于多种类型类型安全 :在编译时进行类型检查性能优化 :编译器可以为特定类型生成优化的代码灵活性 :可以处理各种类型,包括自定义类型函数模板 函数模板是一种通用的函数定义,它可以操作不同类型的参数。
基本语法 1 2 3 4 template <typename T> T functionName (T parameters) { }
其中,typename关键字可以替换为class,它们的含义相同。
示例:最大值函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> template <typename T>T max (T a, T b) { return (a > b) ? a : b; } int main () { int i1 = 10 , i2 = 20 ; std::cout << "Max of " << i1 << " and " << i2 << " is " << max (i1, i2) << std::endl; double d1 = 3.14 , d2 = 2.71 ; std::cout << "Max of " << d1 << " and " << d2 << " is " << max (d1, d2) << std::endl; std::string s1 = "hello" , s2 = "world" ; std::cout << "Max of \"" << s1 << "\" and \"" << s2 << "\" is \"" << max (s1, s2) << "\"" << std::endl; return 0 ; }
模板参数推导 编译器可以根据函数调用的实参类型自动推导模板参数类型,无需显式指定。
1 2 3 4 5 6 7 8 max (10 , 20 );max (3.14 , 2.71 );max (std::string ("hello" ), std::string ("world" ));
显式实例化 我们也可以显式指定模板参数类型:
1 2 3 4 5 max <int >(10 , 20 );max <double >(3.14 , 2.71 );
多模板参数 函数模板可以有多个模板参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> template <typename T1, typename T2>void printPair (T1 first, T2 second) { std::cout << "Pair: (" << first << ", " << second << ")" << std::endl; } int main () { printPair (10 , 3.14 ); printPair ("hello" , true ); printPair (100 , "world" ); return 0 ; }
模板默认参数 C++11及以后,函数模板可以有默认参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <string> template <typename T1, typename T2 = int >void printWithDefault (T1 value, T2 count = 1 ) { for (int i = 0 ; i < count; i++) { std::cout << value << " " ; } std::cout << std::endl; } int main () { printWithDefault ("Hello" ); printWithDefault (42 , 3 ); printWithDefault (3.14 , 2 ); printWithDefault (true , std::string ("times" )); return 0 ; }
类模板 类模板是一种通用的类定义,它可以根据不同的类型参数生成具体的类。
基本语法 1 2 3 4 template <typename T>class ClassName { };
示例:栈类模板 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 #include <iostream> #include <vector> template <typename T>class Stack {private : std::vector<T> elements; public : void push (const T& item) { elements.push_back (item); } void pop () { if (!empty ()) { elements.pop_back (); } } T& top () { return elements.back (); } bool empty () const { return elements.empty (); } size_t size () const { return elements.size (); } }; int main () { Stack<int > intStack; intStack.push (10 ); intStack.push (20 ); intStack.push (30 ); std::cout << "Int Stack size: " << intStack.size () << std::endl; std::cout << "Top element: " << intStack.top () << std::endl; intStack.pop (); std::cout << "After pop, top element: " << intStack.top () << std::endl; Stack<std::string> stringStack; stringStack.push ("hello" ); stringStack.push ("world" ); std::cout << "\nString Stack size: " << stringStack.size () << std::endl; std::cout << "Top element: " << stringStack.top () << std::endl; return 0 ; }
类模板的成员函数 类模板的成员函数可以在类内部定义,也可以在类外部定义。在类外部定义时,需要使用模板参数:
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 #include <iostream> template <typename T>class Pair {private : T first; T second; public : Pair (T f, T s) : first (f), second (s) {} void print () const { std::cout << "(" << first << ", " << second << ")" << std::endl; } T getFirst () const ; T getSecond () const ; }; template <typename T>T Pair<T>::getFirst () const { return first; } template <typename T>T Pair<T>::getSecond () const { return second; } int main () { Pair<int > intPair (10 , 20 ) ; intPair.print (); std::cout << "First: " << intPair.getFirst () << std::endl; std::cout << "Second: " << intPair.getSecond () << std::endl; Pair<double > doublePair (3.14 , 2.71 ) ; doublePair.print (); return 0 ; }
类模板的默认参数 类模板也可以有默认参数:
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 #include <iostream> #include <vector> template <typename T, typename Container = std::vector<T>>class Queue {private : Container elements; public : void push (const T& item) { elements.push_back (item); } void pop () { if (!empty ()) { elements.erase (elements.begin ()); } } T& front () { return elements.front (); } bool empty () const { return elements.empty (); } size_t size () const { return elements.size (); } }; int main () { Queue<int > defaultQueue; defaultQueue.push (10 ); defaultQueue.push (20 ); std::cout << "Default queue front: " << defaultQueue.front () << std::endl; return 0 ; }
模板特化 模板特化允许我们为特定类型提供自定义的模板实现。
全特化 全特化是为特定类型完全重写模板:
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 #include <iostream> #include <string> template <typename T>class Printer {public : void print (const T& value) { std::cout << "General template: " << value << std::endl; } }; template <>class Printer <int > {public : void print (const int & value) { std::cout << "Specialized for int: " << value << std::endl; } }; template <>class Printer <std::string> {public : void print (const std::string& value) { std::cout << "Specialized for string: '" << value << "'" << std::endl; } }; int main () { Printer<double > doublePrinter; doublePrinter.print (3.14 ); Printer<int > intPrinter; intPrinter.print (42 ); Printer<std::string> stringPrinter; stringPrinter.print ("hello" ); return 0 ; }
偏特化 偏特化是为模板参数的子集提供自定义实现:
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 #include <iostream> template <typename T1, typename T2>class Pair {public : void print () { std::cout << "General template: T1 and T2" << std::endl; } }; template <typename T1>class Pair <T1, int > {public : void print () { std::cout << "Partial specialization: T1 and int" << std::endl; } }; template <typename T1, typename T2>class Pair <T1*, T2*> {public : void print () { std::cout << "Partial specialization: T1* and T2*" << std::endl; } }; int main () { Pair<double , std::string> p1; p1. print (); Pair<double , int > p2; p2. print (); Pair<int *, double *> p3; p3. print (); return 0 ; }
可变参数模板 可变参数模板允许模板接受任意数量的模板参数,这是C++11引入的特性。
基本语法 1 2 3 4 template <typename ... Args>void function (Args... args) { }
其中,Args是模板参数包,args是函数参数包。
示例:打印任意数量的参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> void print () { std::cout << std::endl; } template <typename T, typename ... Args>void print (T first, Args... rest) { std::cout << first << " " ; print (rest...); } int main () { print (10 , 3.14 , "hello" , true ); print ("world" , 42 ); print (); return 0 ; }
折叠表达式 C++17引入了折叠表达式,简化了可变参数模板的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> template <typename ... Args>void print (Args... args) { (std::cout << ... << args) << std::endl; } template <typename ... Args>void printWithSeparator (Args... args) { ((std::cout << args << ", " ), ...) << "\b\b\n" ; } int main () { print (10 , 3.14 , "hello" ); printWithSeparator (10 , 3.14 , "hello" ); return 0 ; }
模板元编程 模板元编程是一种在编译时执行计算的技术,它使用模板和常量表达式来生成代码。
基本概念 编译时计算 :在编译期间执行计算,而不是运行时元函数 :返回类型或值的模板模板递归 :使用模板特化作为递归终止条件示例:编译时计算阶乘 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> template <int N>struct Factorial { static constexpr int value = N * Factorial<N-1 >::value; }; template <>struct Factorial <0 > { static constexpr int value = 1 ; }; int main () { constexpr int fact5 = Factorial<5 >::value; constexpr int fact10 = Factorial<10 >::value; std::cout << "5! = " << fact5 << std::endl; std::cout << "10! = " << fact10 << std::endl; return 0 ; }
示例:类型 traits 类型traits是一种用于查询类型属性的模板元编程技术:
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 #include <iostream> #include <type_traits> template <typename T>struct is_pointer { static constexpr bool value = false ; }; template <typename T>struct is_pointer <T*> { static constexpr bool value = true ; }; template <typename T>void printTypeInfo () { std::cout << "Type is pointer: " << is_pointer<T>::value << std::endl; std::cout << "Type is integral: " << std::is_integral<T>::value << std::endl; std::cout << "Type is floating point: " << std::is_floating_point<T>::value << std::endl; } int main () { std::cout << "int:" << std::endl; printTypeInfo <int >(); std::cout << "\ndouble:" << std::endl; printTypeInfo <double >(); std::cout << "\nint*:" << std::endl; printTypeInfo <int *>(); return 0 ; }
C++20模板增强 C++20对模板系统进行了多项重要增强,使模板编程更加灵活和强大。
模板Lambda C++20允许在lambda表达式中使用模板参数:
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 #include <iostream> int main () { auto add = []<typename T>(T a, T b) { return a + b; }; std::cout << "Add integers: " << add (1 , 2 ) << std::endl; std::cout << "Add doubles: " << add (1.5 , 2.5 ) << std::endl; std::cout << "Add strings: " << add (std::string ("Hello" ), std::string (" World" )) << std::endl; auto multiply = []<typename T>(T a, T b) requires std::is_arithmetic_v<T> { return a * b; }; std::cout << "Multiply integers: " << multiply (3 , 4 ) << std::endl; std::cout << "Multiply doubles: " << multiply (2.5 , 4.0 ) << std::endl; auto print = []<typename ... Args>(Args&&... args) { (std::cout << ... << args) << std::endl; }; print ("The answer is " , 42 , ", and pi is " , 3.14159 ); return 0 ; }
概念(Concepts) 概念是C++20引入的一种约束模板参数的机制,它允许我们定义模板参数必须满足的条件:
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 #include <iostream> #include <concepts> template <typename T> concept Arithmetic = std::is_arithmetic_v<T>;template <typename T>concept Printable = requires (T t) { { std::cout << t } -> std::same_as<std::ostream&>; }; template <Arithmetic T>T sum (T a, T b) { return a + b; } template <Printable T>void print (T value) { std::cout << value << std::endl; } template <typename T>concept ArithmeticAndPrintable = Arithmetic<T> && Printable<T>;template <ArithmeticAndPrintable T>void printSum (T a, T b) { std::cout << "Sum: " << sum (a, b) << std::endl; } int main () { printSum (10 , 20 ); printSum (3.14 , 2.71 ); return 0 ; }
Requires表达式 Requires表达式是C++20引入的一种检查模板参数是否满足特定条件的机制:
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 #include <iostream> #include <concepts> template <typename T>requires std::integral<T> || std::floating_point<T> T absolute (T value) { return value < 0 ? -value : value; } template <std::integral T>T square (T value) { return value * value; } template <typename T>requires std::copyable<T> && requires (T a, T b) { { a + b } -> std::same_as<T>; { a * b } -> std::same_as<T>; } T sumAndProduct (T a, T b) { return a + b * a; } int main () { std::cout << "Absolute value of -5: " << absolute (-5 ) << std::endl; std::cout << "Absolute value of -3.14: " << absolute (-3.14 ) << std::endl; std::cout << "Square of 5: " << square (5 ) << std::endl; std::cout << "Sum and product of 3 and 4: " << sumAndProduct (3 , 4 ) << std::endl; return 0 ; }
模板编程最佳实践 1. 命名约定 模板参数 :使用大写字母开头的名称,如T、U、V或更具描述性的名称如ElementType模板类 :清晰地表明它们是模板,如Vector<T>、Map<K, V>2. 代码组织 头文件 :模板定义通常放在头文件中,因为它们需要在使用时实例化分离声明和实现 :对于大型模板,可以使用显式实例化来分离声明和实现命名空间 :将模板放在适当的命名空间中,避免名称冲突3. 性能考虑 模板特化 :为常用类型提供特化版本,优化性能避免过度使用 :模板会增加编译时间和二进制大小,避免不必要的模板使用内联 :模板函数默认是内联的,合理使用可以提高性能4. 可读性和可维护性 注释 :为模板参数添加注释,说明它们的预期类型和约束概念 :使用C++20的概念来清晰地表达模板参数的约束简单性 :保持模板简洁,避免过度复杂的模板元编程5. 调试技巧 显式实例化 :使用显式实例化来捕获模板错误编译错误 :理解模板编译错误的含义,通常会显示完整的模板实例化过程调试器 :使用调试器查看模板实例化后的代码常见模板编程错误 1. 模板参数推导失败 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 template <typename T>void print (T a, T b) { std::cout << a << " " << b << std::endl; } print (10 , 3.14 ); print <double >(10 , 3.14 );template <typename T1, typename T2>void print (T1 a, T2 b) { std::cout << a << " " << b << std::endl; }
2. 模板实例化错误 1 2 3 4 5 6 7 template <typename T>T max (T a, T b) { return a > b ? a : b; }
3. 模板特化顺序 1 2 3 4 5 6 7 8 9 10 11 12 template <typename T>class MyClass { };MyClass<int > obj; template <>class MyClass <int > { };
4. 模板参数依赖 1 2 3 4 5 6 7 8 9 10 11 template <typename T> void func () { T::type x; } template <typename T> void func () { typename T::type x; }
模板编程的应用场景 1. 容器类 标准模板库(STL)中的容器都是模板类:
1 2 3 std::vector<int > vec; std::map<std::string, int > map; std::set<double > set;
2. 算法 STL中的算法都是模板函数:
1 2 3 std::sort (vec.begin (), vec.end ()); std::find (vec.begin (), vec.end (), 42 ); std::for_each(vec.begin (), vec.end (), [](int x) { std::cout << x << "" ; });
3. 智能指针 智能指针也是模板类:
1 2 std::unique_ptr<int > up (new int (42 )) ;std::shared_ptr<std::string> sp = std::make_shared <std::string>("hello" );
4. 类型 traits 类型traits用于查询和修改类型属性:
1 2 3 std::is_integral<int >::value; std::is_same<int , int >::value; std::remove_reference<int &>::type;
5. 元编程库 Boost等库提供了丰富的元编程工具:
1 2 boost::mpl::vector<int , double , std::string> types; boost::mpl::size<types>::value;
总结 模板是C++中实现泛型编程的强大工具,它允许我们编写与类型无关的代码,提高代码的重用性和灵活性。本章介绍了:
模板的基本概念 :函数模板和类模板模板特化 :全特化和偏特化可变参数模板 :处理任意数量的参数模板元编程 :在编译时执行计算C++20模板增强 :模板lambda、概念、requires表达式模板编程最佳实践 :命名约定、代码组织、性能考虑常见错误 :模板参数推导失败、实例化错误等应用场景 :容器、算法、智能指针等模板编程是C++的高级特性,掌握它需要一定的实践经验。通过不断学习和使用模板,你将能够编写更加灵活、高效、可重用的C++代码。