显式拷贝构造函数的行为和实际用途

最近的一个问题让我想知道显式拷贝构造函数。 这里是我尝试在Visual Studio 2005下编译的示例代码:

struct A { A() {} explicit A(const A &) {} }; // #1 > Compilation error (expected behavior) A retByValue() { return A(); } // #2 > Compiles just fine, but why ? void passByValue(A a) { } int main() { A a; A b(a); // #3 > explicit copy construction : OK (expected behavior) A c = a; // #4 > implicit copy construction : KO (expected behavior) // Added after multiple comments : not an error according to VS 2005. passByValue(a); return 0; } 

现在提问:

  • 标准允许#2吗? 如果是,那么描述这种情况的相关部分是什么?
  • 你知道任何明确的复制构造函数的实际用途吗?

[编辑]我刚刚在MSDN上发现了一个有趣的链接,和主函数中的一个神秘的评论:“c被复制了”(好像很明显)。 正如Oli Charlesworth指出的那样:gcc不会编译这些代码,我相信他是对的。

我相信C ++ 03的相关部分是§12.3.12 :

一个显式的构造函数就像非显式构造函数一样构造对象,但是只有在明确使用直接初始化语法( 8.5 )或者强制types( 5.2.9,5.4 )的情况下才这样做。 默认的构造函数可能是一个显式的构造函数; 这样的构造函数将用于执行默认初始化或值初始化( 8.5 )。

和§8.5 12:

在parameter passing,函数返回,抛出exception( 15.1 ),处理exception( 15.3 )和大括号初始化列表( 8.5.1 )中发生的初始化被称为复制初始化,并且等同于表单

  T x = a; 

新expression式( 5.3.4 ),static_castexpression式( 5.2.9 ),函数表示式types转换( 5.2.3 )以及基本成员初始化器( 12.6.2 )中发生的初始化称为直接初始化,相当于表格

  T x(a); 

调用passByValue(a)涉及到复制初始化,而不是直接初始化,因此应该是一个错误,根据C ++ 03§12.3.1 2。

passByValue的定义是可以的,因为没有复制A对象的语句。 在retByValue的定义中,当然有一个复制A对象的返回语句。

在C ++ 11之前,使复制构造器明确的实际应用是实际上使得类不可复制的一部分。

危险之处在于,尽pipe你声明了copy构造函数是私有的,并没有实现它,但是如果你不小心将它拷贝到朋友或者类本身,编译器将不会把它捡起来,你只会得到一个难以find的链接错误。

因为编译器可能会挑选出你无意的副本,并指向你正在执行的实际行,所以明确地说明了这一点,从而减less了这个机会。

在C ++ 11(和14)中,使用=delete语法时不需要执行此操作,因为即使在类本身或朋友中复制,也会出现编译器错误。

要扩展@MSalters的答案(这是正确的),如果你要添加passByValue(a); 到你的main()函数,编译器抱怨它。

显式拷贝构造函数用于防止这种情况发生,也就是阻止函数调用中的资源隐式拷贝等等(实质上它强制用户通过引用而不是按值传递)。