1 模板右尖括号的改进
在 C++11 之前,模板的解析和实例化过程中,右尖括号 > 的处理有时会导致一些意外的结果,特别是在嵌套模板或模板模板参数中。这是因为 C++ 编译器通常会试图"查看前方"来确定何时结束模板参数的列表,这有时会导致解析错误。
C++11 对模板的右尖括号处理进行了改进,引入了两个新的规则:
- 列表初始化:当编译器在模板参数列表中遇到 > 时,它会首先尝试将其解释为列表初始化的结束符,而不是模板参数列表的结束符。这有助于解析像 std::vector<std::initializer_list<int>>这样的类型。
- 模板参数推断:编译器在解析模板参数时,会进行更多的上下文检查来推断正确的解析方式。这有助于减少由于右尖括号导致的解析歧义。
下面是一些示例和解释:
示例 1:列表初始化
在 C++11 之前,如果需要这样声明一个 std::vector,其元素类型为 std::initializer_list<int>:
cpp
std::vector<std::initializer_list<int> > vec;
注意这里使用了额外的空格来避免编译器错误地将 > 解释为模板参数列表的结束符。
在 C++11 中,则可以这样写:
cpp
std::vector<std::initializer_list<int>> vec;
现在编译器会正确地解析它,而不需要额外的空格。
示例 2:模板模板参数
考虑以下嵌套模板的声明:
cpp
template<template<typename> class Outer, typename Inner>
struct NestedTemplate {
Outer<Inner> obj;
};
template<typename T>
struct MyTemplate {
T value;
};
int main() {
NestedTemplate<MyTemplate, int> nt;
return 0;
}
在 C++11 之前,这样的代码可能会导致解析错误,因为编译器可能会错误地将 MyTemplate, int 解释为 NestedTemplate 的模板参数列表的结束。然而,在 C++11 中,由于引入了新的解析规则,这段代码可以正确编译。
如何启用这些改进
这些改进是 C++11 标准的一部分,因此你只需要确保你的编译器支持 C++11 或更高版本的标准。大多数现代编译器都默认支持 C++11,或者你可以通过编译器的命令行选项来明确指定使用 C++11(例如,在 GCC 和 Clang 中使用 -std=c++11)。
总体而言,C++11 对模板右尖括号的处理进行了改进,使得模板的声明和实例化更加直观和易于理解。通过引入列表初始化和模板参数推断的规则,C++11 减少了由于右尖括号导致的解析错误和歧义。使用 C++11 或更高版本可以利用这些改进来编写更清晰、更健壮的模板代码。
2 模板别名
C++11 的模板别名(Template Aliases)是一个新特性,它允许你为模板类型或模板函数创建一个别名,从而简化复杂模板类型的声明和使用。模板别名不仅提高了代码的可读性和可维护性,还使得模板的使用更加灵活。
2.1 模板类型别名
模板类型别名允许为复杂的模板类型定义一个简洁的名称。这特别适用于那些嵌套或带有大量模板参数的类型。
语法如下:
cpp
template<typename... Args>
using AliasName = OriginalTemplateType<Args...>;
其中,AliasName 是定义的别名,OriginalTemplateType 是原始的模板类型,Args... 是模板参数。
示例:
假设有一个模板类 std::vector,它接受一个类型参数。则可以为这个模板类创建一个别名,以便更简洁地表示特定类型的 vector:
cpp
template<typename T>
using Vec = std::vector<T>;
int main()
{
Vec<int> intVec; // 等同于 std::vector<int> intVec;
Vec<double> doubleVec; // 等同于 std::vector<double> doubleVec;
return 0;
}
在这个例子中,Vec 是 std::vector 的一个别名。使用 Vec 可以避免每次都需要写完整的 std::vector 类型,并且使得代码更加清晰。
2.2 模板函数别名
除了模板类型别名外,C++11 还允许为模板函数创建别名。这在处理重载函数或复杂函数签名时特别有用。
语法如下:
cpp
template<typename... Args>
using AliasFunc = ReturnType OriginalFunctionName(Args...);
其中,AliasFunc 是函数别名,OriginalFunctionName 是原始函数名,ReturnType 是函数的返回类型,Args... 是函数的模板参数。
示例:
假设有一个模板函数,它接受两个相同类型的参数并返回它们的和:
cpp
template<typename T>
T add(T a, T b) {
return a + b;
}
则可以为这个模板函数创建一个别名:
cpp
template<typename T>
using AddFunc = T(*)(T, T);
int main()
{
AddFunc<int> addInts = add; // addInts 是指向 add 函数的指针
int sum = addInts(5, 3); // 调用 add 函数并计算 5 + 3
return 0;
}
在这个例子中,AddFunc 是 add 函数的别名,它是一个函数指针类型。通过 AddFunc<int> addInts = add;,创建了一个指向 add<int> 函数的指针 addInts。然后,可以像调用普通函数一样调用 addInts。
2.3 总结
C++11 的模板别名特性为复杂的模板类型和函数提供了一个简洁的表示方式,提高了代码的可读性和可维护性。通过使用模板别名,可以避免在代码中重复编写冗长的模板类型和函数签名,使得代码更加清晰和易于理解。在实际开发中,可以根据需要灵活运用模板别名来简化复杂的模板用法。
3 默认模板参数
C++11 的默认模板参数允许在模板声明中为模板参数提供默认值。这大大增加了模板的灵活性和易用性。默认模板参数使得模板函数和模板类在调用或实例化时,如果未提供某些模板参数,则可以使用默认值。
3.1 模板函数默认参数
在 C++11 之前,模板函数是不支持默认参数的。然而,从 C++11 开始,这个限制被解除了。现在,可以为模板函数的模板参数提供默认值。
语法:
cpp
template<typename T = DefaultType>
ReturnType functionName(Parameters);
其中,DefaultType 是模板参数 T 的默认值,ReturnType 是函数的返回类型,functionName 是函数名,Parameters 是函数的参数列表。
示例:
cpp
#include <iostream>
#include <typeinfo>
template<typename T = int>
void printSize(T t = 0) {
std::cout << "The sizeof " << typeid(T).name() << " is: " << sizeof(t) << std::endl;
}
int main()
{
printSize(); // 使用默认模板参数和函数参数,输出 int 的大小
printSize(1.0); // 使用浮点数推导模板参数,输出 double 的大小
printSize<char>('a'); // 指定模板参数为 char,输出 char 的大小
return 0;
}
上面代码的输出为:
The sizeof int is: 4
The sizeof double is: 8
The sizeof char is: 1
在上面的示例中,printSize 函数有一个默认模板参数 T,其默认值为 int。同时,该函数还有一个默认函数参数 t,其默认值为 0。这使得调用者可以选择性地提供模板参数和函数参数。
3.2 模板类默认参数
与模板函数类似,模板类也可以有默认模板参数。这对于创建更加通用且易于使用的模板类非常有用。
语法:
cpp
template<typename T1 = DefaultType1, typename T2 = DefaultType2, ...>
class ClassName {
// 类成员和方法
};
其中,DefaultType1、DefaultType2 等是模板参数 T1、T2 等的默认值,ClassName 是类名。
示例:
cpp
#include <iostream>
#include <typeinfo>
#include <string>
template<typename T1 = int, typename T2 = double>
class Pair {
public:
T1 first;
T2 second;
Pair(T1 f = T1(), T2 s = T2()) : first(f), second(s) {}
};
int main()
{
Pair<> p1; // 使用默认模板参数,T1 为 int,T2 为 double
Pair<std::string> p2; // 使用 std::string 作为 T1,T2 为默认的 double
Pair<char, long> p3('c', 123L); // 明确指定两个模板参数
return 0;
}
在上面的示例中,Pair 类有两个默认模板参数 T1 和 T2,分别默认为 int 和 double。类的构造函数也有默认参数,使得对象的创建更加灵活。
3.3 模板类默认参数在模板类特化中的应用
在 C++ 中,模板类特化是一种为特定类型或类型组合提供模板类的特殊实现的技术。特化可以用于优化性能、添加特定类型的特定行为或提供类型无关的默认实现之外的特定实现。当与 C++11 的默认模板参数结合使用时,模板类特化可以变得更加灵活和强大。
默认模板参数允许在模板声明时为模板参数提供默认值。这样,在实例化模板类时,如果没有提供某些模板参数,编译器会使用默认值。当与模板类特化结合时,可以创建出既具有通用性又具有特定类型优化的代码结构。
以下是一些结合使用默认参数和特化应用场景:
(1)提供默认行为: 同时为特定类型优化:通过默认模板参数,可以为模板类提供一个通用的默认实现。然后,通过特化这个模板类,可以为特定类型提供优化的实现。
(2)处理复杂类型组合: 当模板类涉及多个类型参数时,可以使用部分特化来处理特定的类型组合。结合默认参数,可以进一步减少需要特化的情况,使代码更加简洁。
(3)扩展模板类的功能: 通过特化,可以为模板类添加新的成员函数或修改现有成员函数的行为。结合默认参数,可以确保在没有特化的情况下,模板类仍然具有可用的默认实现。
如下为样例代码:
cpp
#include <iostream>
#include <string>
// 模板类定义,带有默认模板参数
template<typename T1 = int, typename T2 = std::string>
class MyClass {
public:
void print() {
std::cout << "Default implementation for MyClass<" << typeid(T1).name() << ", " << typeid(T2).name() << ">" << std::endl;
}
};
// 模板类特化,针对特定类型组合的优化实现
template<>
class MyClass<int, int> {
public:
void print() {
std::cout << "Specialized implementation for MyClass<int, int>" << std::endl;
}
};
int main()
{
// 使用默认模板参数实例化 MyClass
MyClass<> obj1;
obj1.print(); // 输出默认实现
// 使用特定类型组合实例化 MyClass,触发特化
MyClass<int, int> obj2;
obj2.print(); // 输出特化实现
// 使用其他类型组合实例化 MyClass
MyClass<double, std::string> obj3;
obj3.print(); // 输出默认实现,因为没有针对这种类型组合的特化
return 0;
}
上面代码的输出为:
Default implementation for MyClass<int, class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >>
Specialized implementation for MyClass<int, int>
Default implementation for MyClass<double, class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >>
在上面的示例中,MyClass 是一个带有两个默认模板参数的模板类。当使用默认参数实例化时,会调用默认实现。然而,当使用 int 和 int 作为类型参数时,会触发特化版本的 MyClass,从而调用优化后的 print 方法。对于其他类型组合,仍然使用默认实现。
3.4 总结
C++11 的默认模板参数特性使得模板函数和模板类的使用更加简洁和灵活。通过为模板参数提供默认值,可以减少模板实例化时的冗余代码,并提高代码的可读性和可维护性。在实际开发中,合理利用默认模板参数可以极大地提高编程效率。