C++的直接初始化与复制初始化的区别

本文转载自:负一的平方根 https://sqrt-1.me/?p=241          修改了部分地方

C++中的直接初始化指的是直接调用类的构造函数进行初始化,如下例如

string a; //调用默认构造函数
string a("hello"); //调用参数为const char *类型的构造函数
string b(a); //调用拷贝构造函数

复制初始化指的是用“=”号来初始化对象,例如

string a="hello";   相当于隐式调用构造函数
string b=a;         注意这里相当于隐式调用拷贝构造函数,而不是调用赋值运算符函数

在上面的例子中,这两种写法是完全等效的,但是直接初始化和复制初始化在一些情况下还是有区别的。

根据C++的标准,直接初始化就是直接调用类的构造函数来初始化对象,例如在string a(“hello”)中,string类的string(const char *)构造函数会被调用,a被直接初始化。

然而根据标准,复制初始化应该是先调用对应的构造函数创建一个临时对象,再调用拷贝构造函数将临时对象拷贝给要创建的对象。例如在string a=”hello”中,string类的string(const char *)构造函数会被首先调用,创建一个临时对象,然后拷贝构造函数将这个临时对象复制到a。

标准还规定,为了提高效率,允许编译器跳过创建临时对象这一步,直接调用构造函数构造要创建的对象,这样就完全等价于直接初始化了。

经过我的测试,常见的编译器(gcc和VC++)都会直接跳过创建临时对象这一步,即使没有打开任何优化选项。

这样一来,也许你就会认为他们完全等同了,其实不然。下面分析两种情况。

1、当拷贝构造函数为private时。

虽然编译器会跳过创建临时对象这一步,但是这个类必须要能够正确的调用指定的构造函数和拷贝构造函数才能编译通过。优化是编译器采取的措施,但是程序必须使得不优化时代码也符合语法。

C++中有一些系统类型不允许复制,例如iostream和fstream这样的类。如果你想把自定义的类设置为不允许复制,把拷贝构造函数和赋值运算符声明为private即可,前面所说的系统类型也是这样做的。

我们用ifstream举例说明直接初始化和复制初始化的区别。用一个字符串来构造ifstream对象,即打开以字符串为文件名的文件输入流。

ifstream file("filename"); //编译通过
ifstream file="filename"; //编译出错,ifstream类的拷贝构造函数为private

我们来分析一下为什么第二个语句会编译出错。根据C++标准,第二个语句应该做以下两件事情:用”filename”初始化一个临时的ifstream对象,把临时的对象用拷贝构造函数复制给file。由于拷贝构造函数是private,所以没有权限调用此函数,编译出错。如下图是Visual Studio 2017的C++编译器  报的错误

2、当拷贝构造函数为explicit,或者指定的构造函数为explicit时。

C++中如果一个构造函数为explicit,那么只能显式调用这个构造函数,把这个构造函数用作“隐式类型转换”是不可以的。

我们定义一个类A,然后进行以下测试

class A{
public:
    A(int a){}
    A(const A &a){}
};

int main(){
    A a(1);
    A b=1;
    return 0;
}

可以编译通过。然后我们把参数为int的构造函数改为explicit,代码如下

class A{
public:
    explicit A(int a){}
    A(const A &a){}
};

int main(){
    A a(1);
    A b=1;
    return 0;
}

“A a(1)”可以编译通过,“A b=1”这句报错,错误信息是“conversion from ‘int’ to non-scalar type ‘A’ requested”。这意味着编译器找不到int类型转换成A类型的转换函数了。如果不加explicit,参数为int的构造函数可以用于自动把int转换为A类型,但是现在不可以了,explicit声明无法做隐式转换。第一句的“A a(1)”有参数为int的构造函数的显式调用,而第二句“A b=1”没有显式调用。

下面我们只把拷贝构造函数改成explicit

class A{
public:
    A(int a){}
    explicit A(const A &a){}
};

int main(){
    A a(1);
    A b=1;
    return 0;
}

还是第二句编译错误。g++下错误信息是“no matching function for call to ‘A::A(A)’”,这说明编译器找不到拷贝构造函数了。和前面第一种情况中说明的一样,编译器在把临时对象复制到b中时,需要调用A类的拷贝构造函数。现在拷贝构造函数是explicit的,只能显式的调用,不能隐含调用。第二句没有“A b(temp)”这样显式复制对象,所以编译出错。

注意:Visual Studio 2017的C++编译器下:第二句编译没报错通过了    vs 并没有遵循标准,而是不考虑拷贝构造函数是否为private,直接编译通过。

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值