3

考虑以下代码:

#include<iostream>
using namespace std;
class A{
public:
  A()=default;
  A(int a){cout<<"A(int) called"<<endl;}
  A(const A&a);
  ~A(){cout<<"~A() called"<<endl;}
};
A::A(const A&a){
  cout<<"A(const A&) called"<<endl;
}
int main(){
   A a = 1;
}

当我使用 g++8.1 编译-fno-elide-constructors取消 RVO 时,输出为:

A(int) called
A(const A&) called
~A() called
~A() called

我知道这是一种叫做转换构造函数的东西,一种从其参数类型到其类类型的隐式转换。

似乎首先一个临时对象是由 构造的A(int),其次是a对象是由复制构造函数构造的A(const A&)

但是当我修改我的代码时:

#include<iostream>
using namespace std;
class A{
public:
  A()=default;
  A(int a){cout<<"A(int) called"<<endl;}
  explicit A(const A&a); //use explicit
  ~A(){cout<<"~A() called"<<endl;}
};
A::A(const A&a){
  cout<<"A(const A&) called"<<endl;
}
int main(){
   //A a = A(1); //error:no constructor match
   A b = 1; //ok
}

对象是显式b复制构造的,这让我很困惑?!即使我使用复制初始化? 当我删除复制构造函数时,它不会按预期工作。
A(const A&a)=delete;

但是,当我使用 VS2017 时,情况就不同了。隐式转换A a = 1;与复制构造函数无关。即使我删除了 cc,它也照常工作。

4

2 回答 2

1

和...之间的不同

A a = A(1);

A b = 1;

是在第二种情况下,适用的效果是@songyuanyao的答案中描述的效果。但是,第一种情况的效果是另一种情况:

ifT是一个类类型和 other is 类型的 cv 非限定版本T或派生自 的类,T检查 的非显式构造函数T并通过重载决议选择最佳匹配。然后调用构造函数来初始化对象。

这就是为什么它不适用于explicit复制构造函数。

于 2020-07-21T07:26:52.083 回答
1

这里复制初始化的效果是,

(强调我的)

如果 T 是类类型,并且 other 的类型的 cv 非限定版本不是 T 或派生自 T,或者如果 T 是非类类型,但 other 的类型是类类型,则用户定义的转换序列检查可以从 other 的类型转换为 T 的类型(或者如果 T 是类类型并且转换函数可用,则转换为从 T 派生的类型),并通过重载决议选择最佳的类型。转换的结果,prvalue temporary (until C++17) prvalue expression (since C++17)如果使用了转换构造函数,则用于直接初始化对象The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used. (until C++17)

请注意,ojbect 是从转换后的A(from int) 直接初始化的,复制构造函数explicit在此处是否标记为无关紧要。


顺便说一句:由于 C++17 由于强制复制省略,复制构造将被完全省略。

在以下情况下,编译器必须省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察到的副作用。对象直接构建到存储中,否则它们将被复制/移动到。复制/移动构造函数不需要存在或可访问:

于 2020-07-21T07:22:53.677 回答