Конструктор копирования не вызывается при инициализации объекта с возвращаемым значением функции

Рассмотрим следующий код:

#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 раза. Итак, если в случае, описанном в предыдущем абзаце, конструктор копии временного объекта опущен, то почему он не опускается в этом случае?


comment
Как я изучал этот материал, когда узнавал обо всем этом, было std::cout << "Copy constructor: " << (void*)b << " to " << (void*)this << std::endl; и std::cout << "Constructing " << (void*)this << std::endl. Бонусные баллы за добавление ходов в C ++ 11.   -  person IdeaHat    schedule 14.11.2014


Ответы (2)


#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 конструируется, c копируется непосредственно в b, несмотря на то, что b не передан fun!

person IdeaHat    schedule 14.11.2014

Исключенная копия является копией временного возвращаемого значения в b. Без исключения возвращаемое значение инициализируется из a и копируется в b. Вместо этого временный объект, который в противном случае содержал бы возвращаемое значение, конструируется в b и инициализируется с помощью a. [class.copy] / 31:

когда временный объект класса, который не был привязан к ссылке (12.2), будет скопирован / перемещен в объект класса с тем же типом cv-unqualified, операция копирования / перемещения может быть опущена путем создания временного объекта непосредственно в целевом объекте пропущенной копии / перемещения

Вы можете наблюдать это, если добавите дополнительный вывод в fun:

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

Тогда с элизией вы получите

[…]
весело!
Конструктор копирования

И без:

[…]
весело!
Конструктор копирования
Конструктор копирования

person Columbo    schedule 14.11.2014