为什么不调用复制构造函数

4
这里是一个简单的类头文件和主程序。在主程序中,我认为复制构造函数在三种情况下被调用:初始化(显式复制),作为函数参数按值传递和按值返回函数。但是似乎它并没有在其中一种情况下被调用,我认为是在注释中编号为(3)或(4)的情况。它将在哪些数字(1)-(4)中被调用?谢谢。
X.h:
#include <iostream>

class X
{
public:
    X() {std::cout << "default constructor \n";}
    X(const X& x) { std::cout << "copy constructor \n";}
};

主要内容:

#include "X.h"

X returnX(X b)  // (1) pass by value - call copy constructor?
{
    X c = b;  // (2) explicit copy - call copy constructor?
    return b;  // (3) return by value - call copy constructor?
}

int main()
{
    X a; // calls default constructor

    std::cout << "calling returnX \n\n";
    X d = returnX(a);  // (4) explicit copy - call copy constructor?
    std::cout << "back in main \n";
}

输出:

default constructor
calling returnX

copy constructor
copy constructor
copy constructor 
back in main

4
编译器可以将其优化掉。 - πάντα ῥεῖ
2个回答

7
由于C++中复制操作的频繁发生,而且可能很耗费时间,编译器可以省略某些复制(和移动)构造函数。即使省略的构造函数和/或析构函数具有像程序输出这样的副作用(也就是说,与使用和不使用复制省略的行为不同),也允许进行此复制省略。
根据12.8 [class.copy]第31段的规定,有四个基本的应用复制省略的地方:
1. 在return语句中,直接返回与函数返回类型相同的局部变量时。 2. 在throw语句中,当内部try块内的自动变量被抛出时,可以省略其复制。 3. 当未将临时对象绑定到引用时,可以省略其复制。 4. 当catch子句按值捕获与throw语句中的对象类型相同的对象时,可以省略复制。
虽然确切的规则稍微复杂,但我认为这就是要点。鉴于复制省略的规则相当严格,很容易抑制复制省略:最简单的方法是使用identity()函数。
template <typename T>
T const& identity(T const& object) {
    return object;
}
...
X d = identity(returnX(a));

这个版本也会抑制移动构造,但是使用 T&& 推断类型并适当返回应该可以使移动构造成为可能,但我不确定返回类型和返回语句应该是什么。


警告:此回答早于C++17,其中规则是不同的(尽管结果可能相同)。 - Lightness Races in Orbit

6

第四个数字正在复制临时对象。它是拷贝省略的候选项。也就是说,在某些条件下,即使拷贝构造函数具有副作用,编译器也可以消除对拷贝构造函数的调用。


实际上,3和4都是可以进行复制省略的候选项。输出可能只包含了2个复制构造。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas:我认为 NRVO 对参数是禁止的,不是吗? - Benjamin Lindley
1
@DavidRodríguez-dribeas:参数不允许复制省略。只有本地自动变量,不包括参数,才有资格进行复制省略。但是,可以移动参数(下一段)。 - Dietmar Kühl
@DietmarKühl(和Benjamin):我的错,我把代码读成了return c;而不是return b; - David Rodríguez - dribeas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接