直接初始化
定义
- 从明确的构造函数实参的集合初始化对象
语法
在下列场合进行直接初始化:
以表达式或花括号初始化器列表 (C++11 起)的非空带括号列表初始化
T object ( arg );
T object ( arg1, arg2, ... );
作为列表初始化的一部分,以花括号环绕的单个初始化器初始化非类类型和非数组类型对象时,以初始化提供了初始化器的元素(注意:对于类类型和其他使用花括号初始化器列表的初始化,见
列表初始化
) (C++11起)
T object {
arg };
用函数式转型或以带括号的表达式列表初始化纯右值临时量 (C++17 前)纯右值的结果对象 (C++17 起)
T ( other )
T ( arg1, arg2, ... )
用 static_cast 表达式初始化纯右值临时量 (C++17 前)纯右值的结果对象 (C++17 起)
static_cast< T >( other )
用带有非空初始化器的 new 表达式初始化决议动态存储期的对象
new T(args, ...)
用构造函数初始化器列表初始化基类或非静态成员
Class::Class() : member(args, ...) {
... }
在 lambda 表达式中从按复制捕获的变量初始化闭包对象的成员
[arg](){
... }
直接初始化 VS 拷贝初始化
Primer中的说法
- 当作用于类类型对象时,初始化的复制形式和直接形式有所不同:
- 直接初始化直接调用与实参匹配的构造函数
- 拷贝初始化总是调用赋值构造函数
- 复制初始化首先使用构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象
- 通常直接初始化和复制初始化仅在低级别优化上存在差异,然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质的区别
#include <iostream>
#include <fstream>
using namespace std;
int main(){
ifstream file1("filename");//ok:direct initialization
ifstream file2 = "filename";//error:copy constructor is private”
}
通常的误解
从上面的说法中,我们可以知道:直接初始化不一定要调用复制构造函数,而复制初始化一定要调用复制构造函数
然而,大多数人却认为,直接初始化是构造对象时要调用复制构造函数,而复制初始化是构造对象时要调用赋值操作函数,这其实是一大误解,因为只有对象被创建时才出现初始化,而赋值操作并不应用于对象的创建过程。至于为什么会出现这个误解,可能是因为复制初始化的写法中存在等号(=)吧。
我们来看个例子
string str1 = "first"; //拷贝初始化,编译器允许把这句话改写为string str(“first”),但是string类必须有public的拷贝(移动)构造函数
string str2(10,'a'); //直接初始化
string str3(str2); //直接初始化
string str4 = string(10,'b'); //拷贝初始化
string str5 = str4; //拷贝初始化
string str6 ("strr"); //直接初始化
如果误解调用拷贝构造函数的就是拷贝初始化,所以可能觉得str3也是拷贝初始化,这样就错了~
直接初始化:
- 使用普通的函数匹配就可以完成的初始化叫做直接初始化,
- 也就是说只是直接调用类的构造函数或者拷贝构造函数就能完成初始化的就是直接初始化。
- 而str2和str3分别调用构造函数和拷贝构造函数,因此它们是直接初始化
拷贝初始化:
- 将对象拷贝到正在创建的对象中,如果需要还要进行类型转换。
- 这里也就是间接调用拷贝构造函数,当然大部分情况调用拷贝构造函数,有时也可能调用移动构造函数。
根据上面的例子,我们可能觉得,只有等号"="出现时,才会是拷贝初始化。
其实不然,在下面三个情况下也会发生拷贝初始化。
- 将一个对象作为实参传递给一个非引用类型的实参(引用作为参数的话就可以省略拷贝这一操作,所以有时候可以这样来优化代码)
- 从一个返回类型为非引用的函数返回一个对象
- 花括号初始化一个数组的元素或一个聚合类(struct)的成员
我们在来看一个例子