在C++98时代,我们初始化数组和结构体时已经可以使用花括号语法,但这种便利性并没有扩展到所有场景。C++11引入的列表初始化(也称为统一初始化)彻底改变了这一局面,让{}成为初始化各种类型对象的通用方式。
cpp复制// 传统C++98初始化方式
int arr1[] = {1, 2, 3};
Point p = {1, 2};
// C++11统一初始化语法
int arr2[]{1, 2, 3}; // 省略等号
Point p{1, 2}; // 直接构造
这种语法改进不仅仅是表面上的美观,它带来了几个重要优势:
实际开发中建议:对于简单内置类型可以使用传统赋值方式,复杂对象和容器优先使用{}初始化,这能让代码意图更清晰。
当对自定义类型使用{}初始化时,编译器会执行一系列隐式操作:
cpp复制Date d{2022, 1, 1};
其背后实际发生的步骤是:
这种机制也解释了为什么我们可以在new表达式中使用列表初始化:
cpp复制int* p = new int[5]{1, 2, 3}; // 初始化动态数组
STL容器能够接受任意长度初始化列表的奥秘在于std::initializer_list。这是一个轻量级的包装器,内部包含两个指针,分别指向初始化列表的起始和结束位置。
cpp复制vector<int> v = {1, 2, 3, 4, 5};
编译器处理上述代码的步骤:
值得注意的是,initializer_list只提供const访问,这意味着:
C++不允许直接声明引用的引用,但在模板推导和typedef中可能间接产生这种情况。引用折叠规则如下:
| 组合类型 | 折叠结果 |
|---|---|
| T& & | T& |
| T& && | T& |
| T&& & | T& |
| T&& && | T&& |
这个规则是理解现代C++模板元编程的基础。通过几个典型例子来说明:
cpp复制typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; // int&
lref&& r2 = n; // int&
rref& r3 = n; // int&
rref&& r4 = 1; // int&&
模板函数中的T&&参数之所以被称为"万能引用",是因为它能根据传入实参的类型自动推导为左值引用或右值引用:
cpp复制template<typename T>
void func(T&& arg) {
// arg可能是左值引用或右值引用
}
但这里有个关键问题:无论arg原本是什么引用类型,在函数体内它都是一个左值。这就是需要完美转发(std::forward)的场景:
cpp复制template<typename T>
void wrapper(T&& arg) {
// 错误:丢失了arg的原始值类别
// callee(arg);
// 正确:保持arg的原始值类别
callee(std::forward<T>(arg));
}
std::forward的实现本质上是条件转换:
C++11允许我们显式要求编译器生成默认实现的特殊成员函数:
cpp复制class Widget {
public:
Widget() = default;
Widget(const Widget&) = default;
Widget(Widget&&) = default;
// ...
};
使用=default有几个好处:
=delete不仅可以禁用编译器生成的默认函数,还可以禁用任何函数:
cpp复制class NonCopyable {
public:
NonCopyable(const NonCopyable&) = delete;
void operator=(const NonCopyable&) = delete;
};
// 禁用特定模板实例化
template<typename T>
void process(T value);
template<>
void process<void>(void) = delete;
相比C++98将函数声明为private的方式,=delete具有以下优势:
列表初始化结合移动语义可以显著提升性能:
cpp复制vector<string> createStrings() {
return {"hello", "world", "from", "C++11"};
// 比return vector<string>({"hello",...})更高效
}
关键优化点:
过度使用initializer_list:
cpp复制vector<int> v{100, 2}; // 包含两个元素:100和2
vector<int> v(100, 2); // 100个元素,每个都是2
意外的拷贝:
cpp复制auto list = {1, 2, 3}; // list是initializer_list<int>
vector<int> v1(list); // 拷贝
vector<int> v2(move(list)); // 错误!initializer_list不能移动
完美转发失败场景:
cpp复制template<typename... Args>
void forwarder(Args&&... args) {
target(std::forward<Args>(args)...); // 完美转发
target({1, 2, 3}); // 错误:无法推导initializer_list类型
}
在实际项目中应用这些特性时,建议:
代码规范中明确初始化风格:
模板库开发必须掌握:
特殊成员函数管理:
性能关键路径:
经过多个大型项目的实践验证,合理运用C++11的这些特性可以使代码更简洁、更安全,同时获得显著的性能提升。特别是在模板库开发和资源管理类设计中,这些技术已经成为现代C++编程的标准实践。