Требуется ли конструктор копирования при возврате путем неявного преобразования?
Следующий код компилируется в 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 &);
^
Ответы
Ответ 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
требуется конструктор копирования, потому что вы возвращаетесь по значению.
-
В конце концов, конструктор копирования не будет вызван из-за копирования, но все равно должен быть доступен.