Почему конструктор копирования не вызывается

Вот простой заголовочный файл класса и основная программа. В основной программе я думал, что конструктор копирования вызывается ровно в трех ситуациях: инициализация (явное копирование), передача по значению для аргументов функции и возврат по значению для функций. Тем не менее, кажется, что это не вызывается для одного из них, я думаю, (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

Решение

Поскольку копирование часто происходит в C ++ и, поскольку оно может быть дорогим, компилятору разрешается исключать определенные конструкции копирования (и перемещения). Это разрешение на копирование разрешено, даже если у конструктора elided и / или деструктора есть побочные эффекты, такие как выходные данные в вашей программе (то есть, на самом деле это не оптимизация, поскольку поведение с разрешением копирования и без него отличается).

В соответствии с пунктом 12.8 [class.copy] параграфа 12.8 может быть применено четыре основных места:

  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&& и его возвращение должно сделать возможным создание перемещения, но я не совсем уверен, каким должен быть тип return и оператор return).

7

Другие решения

Номер 4 копирует временный. Это кандидат на копия elision. То есть при определенных условиях компилятору разрешается исключать вызовы конструктора копирования, даже если конструктор копирования имеет побочные эффекты.

6

По вопросам рекламы [email protected]