Подтвердить что ты не робот

Требуется ли конструктор копирования при возврате путем неявного преобразования?

Следующий код компилируется в Visual С++ 2013, но не под GCC или Clang.

Что правильно?
Требуется ли доступный конструктор копирования при возврате объекта через неявное преобразование?

class Noncopyable
{
    Noncopyable(Noncopyable const &);
public:
    Noncopyable(int = 0) { }
};

Noncopyable foo() { return 0; }

int main()
{
    foo();
    return 0;
}

GCC:

error: 'Noncopyable::Noncopyable(const Noncopyable&)' is private
  Noncopyable(Noncopyable const &);
  ^
error: within this context
 Noncopyable foo() { return 0; }

Clang:

error: calling a private constructor of class 'Noncopyable'
Noncopyable foo() { return 0; }
                    ^
note: implicitly declared private here
        Noncopyable(Noncopyable const &);
        ^
warning: C++98 requires an accessible copy constructor for class 'Noncopyable' when binding a reference to a temporary; was private [-Wbind-to-temporary-copy]
Noncopyable foo() { return 0; }
                           ^
note: implicitly declared private here
        Noncopyable(Noncopyable const &);
        ^
4b9b3361

Ответ 1

Когда вы return выражаете, создается временный объект типа возвращаемого значения, инициализируется с этим выражением, а затем перемещается (или копируется, если перемещение не является опцией) в возвращаемое значение. Таким образом, вам нужен доступный экземпляр или механизм перемещения.

Однако можно инициализировать возвращаемое значение напрямую, используя скошенный список. Итак, следующие работы:

Noncopyable foo() { return {0}; }

Аналогичный случай в живом примере.

Ответ 2

12.8 Копирование и перемещение объектов класса [class.copy]

1/ Объект класса может быть скопирован или перенесен двумя способами: путем инициализации (12.1, 8.5), в том числе для аргумента функции (5.2.2) и для возврата значения функции (6.6.3); [...]

В 6.6.3 Оператор return [stmt.return]:

2/[...] Значение выражения неявно преобразуется в возвращаемый тип функции, в которой он появляется. Оператор возврата может включать создание и копирование или перемещение временного объекта (12.2) [...]

и 12.2 Временные объекты [class.temporary]:

1/ В разных контекстах создаются темпы типа класса: привязка ссылки на prvalue (8.5.3), возврат (6.6.3), преобразование, которое создает prvalue (4.1, 5.2.9, 5.2.11, 5.4), [...] Примечание: даже если нет вызова деструктора или копии /move, все семантические ограничения, такие как доступность (раздел 11) и функция удаления (8.4.3), должны быть довольным. [...]

Я бы утвердил, что GCC и clang верны - я даже зашел так далеко, как сказал, что в любое время, когда вы возвращаетесь по значению, возвращаемый тип должен иметь доступный экземпляр или перемещать конструктор.

Логика будет заключаться в том, что создается временное создание для преобразования исходного типа в новый тип (int to Noncopyable), а затем копия этого временного объекта возвращается для функции.

Это очень важно:

Noncopyable foo() { return Noncopyable(0); }

Вы ожидали, что там будет нужна копия? Я бы это сделал.

Ответ 3

  • Функция foo возвращает объект Noncopyable по значению. Таким образом, теоретически должен быть вызван конструктор копирования.

  • Если вы создадите конструктор копирования (т.е. public) и напечатаете сообщение, чтобы отметить его воскрешение, вы увидите, что это сообщение не напечатано DEMO и вызывается только перегруженный оператор преобразования.

  • Это связано с оптимизацией копирования.

  • Таким образом, дело не в том, что для перегруженного оператора преобразования требуется конструктор копирования, а для оператора return foo требуется конструктор копирования, потому что вы возвращаетесь по значению.

  • В конце концов, конструктор копирования не будет вызван из-за копирования, но все равно должен быть доступен.