使用函数返回值初始化对象时未调用拷贝构造函数

9

Consider the following code:

#include <iostream>

using namespace std;

class A
{
    public:
        int a;
        A(): a(5)
        {
           cout << "Constructor\n";
        }
        A(const A &b)
        {
            a = b.a;
            cout << "Copy Constructor\n";
        }
        A fun(A a)
        {
            return a;
        }
};

int main()
{
    A a, c;
    A b = a.fun(c);
    return 0;
}

上述代码使用 g++ file.cpp 编译的输出结果为:
Constructor
Constructor
Copy Constructor
Copy Constructor

以上代码使用 g++ -fno-elide-constructors file.cpp 编译的输出结果如下:
Constructor
Constructor
Copy Constructor
Copy Constructor
Copy Constructor

我知道返回值优化。我的问题是副本构造函数的哪个调用被省略了(在返回期间的临时对象还是返回的对象被复制到b)?
如果省略的副本构造函数是用于创建b的那个,则如何创建b(因为在这种情况下也没有构造函数调用)?
如果我将行 A b = a.fun(c); 替换为 a.fun(c) 并使用第一种方法或第二种方法编译,那么副本构造函数仍然会被调用2次。所以,在上一段中解释的情况下,临时对象的副本构造函数被省略了,为什么在这种情况下它没有被省略?

当我学习这些知识时,我如何检查它们的方式是 std::cout << "Copy constructor: " << (void*)b << " to " << (void*)this << std::endl;std::cout << "Constructing " << (void*)this << std::endl。如果您添加了C++11中的移动语义,将获得额外加分。 - IdeaHat
2个回答

6
#include <iostream>

using namespace std;

class A
{
public:
    int a;
    A(): a(5)
    {
        cout << "Constructing: " << (void *)this << std::endl;
    }
    A(const A &b)
    {
        a = b.a;
        cout << "Copy Constructor: " << (void *)this << " from " << (void *)&b << std::endl;
    }
    A fun(A a)
    {
        return a;
    }
};

int main()
{

    A a, c;
    A b = a.fun(c);

    std::cout << "a:" << (void *)&a << std::endl <<
              "b:" << (void *)&b << std::endl <<
              "c:" << (void *)&c << std::endl;
    return 0;
}

产生:

Constructing: 0x7fffbb377220
Constructing: 0x7fffbb377210
Copy Constructor: 0x7fffbb377230 from 0x7fffbb377210
Copy Constructor: 0x7fffbb377200 from 0x7fffbb377230
a:0x7fffbb377220
b:0x7fffbb377200
c:0x7fffbb377210

因此,它构建 a,构建 c,将 c 复制到一个中间值(函数的参数 a),然后直接将中间值复制到 b,跳过了将 a 复制到返回值中的典型复制。如果按值传递(更改为 A fun(const A& a)),这一点甚至可以更好地证明:
Constructing: 0x7fff8e9642b0
Constructing: 0x7fff8e9642a0
Copy Constructor: 0x7fff8e964290 from 0x7fff8e9642a0
a:0x7fff8e9642b0
b:0x7fff8e964290
c:0x7fff8e9642a0

创建了a,创建了c,尽管b未被传递给函数,但c直接复制到了b!


4
被省略的副本是将临时返回值复制到b中的副本。如果没有省略,则返回值将从 a 初始化并复制到 b 中。相反,否则将构造临时对象以将其初始化为 a ,并将其构造到 b 中。 [class.copy] / 31:

当一个未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv-unqualified类型的类对象时,可以通过直接构造临时对象到省略的复制/移动目标来省略复制/移动操作。

如果在 fun 中添加额外的输出,则可以观察到这一点:

A fun(A a)
{
    cout << "fun!" << endl;
    return a;
}

有了省略,您会得到:

[...]
欢迎!
复制构造函数

没有省略:

[...]
欢迎!
复制构造函数
复制构造函数


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