Следующий код прекрасно компилируется в Visual C ++ 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 &);
^
Когда ты return
выражение, временный объект возвращаемого типа создается, инициализируется с этим выражением, а затем перемещается (или копируется, если перемещение не является опцией), в возвращаемое значение. Так что вам нужен доступный конструктор копирования или перемещения.
Однако возможно инициализировать возвращаемое значение напрямую, используя список в скобках. Итак, следующие работы:
Noncopyable foo() { return {0}; }
Подобный случай в живой пример.
12.8 Копирование и перемещение объектов класса [class.copy]
1 / Объект класса может быть скопирован или перемещается двумя способами: инициализацией (12.1, 8.5), в том числе для аргумента функции
прохождение (5.2.2) и для возврата значения функции (6.6.3); […]
В 6.6.3 Оператор возврата [stmt.return]:
2 / […] Значение выражения неявно
преобразован в тип возврата функции, в которой он появляется. Оператор возврата может включать
строительство и копирование или двигаться временного объекта (12.2) […]
а также 12.2 Временные объекты [класс.время]:
1 / Созданы временные классы в различных контекстах: привязка ссылки к prvalue (8.5.3), возврате
prvalue (6.6.3), преобразование, которое создает prvalue (4.1, 5.2.9, 5.2.11, 5.4), […] Примечание: даже если нет вызова деструктора или конструктора копирования / перемещения, все
семантические ограничения, такие как доступность (пункт 11) и удаление функции (8.4.3), должны
быть довольным. […]
Я бы сказал, что GCC и clang верны — я бы даже сказал, что каждый раз, когда вы возвращаете значение, у возвращаемого типа должен быть доступный конструктор копирования или перемещения.
Логика заключается в том, что временный создается для преобразования исходного типа в новый тип (int
в Noncopyable
), а затем создается копия этого временного объекта для возврата в функцию.
Это так же, как:
Noncopyable foo() { return Noncopyable(0); }
Вы ожидаете, что копия будет требоваться там? Я бы конечно
функция foo
возвращает Noncopyable
объект по значению. Таким образом, теоретически должен быть вызван конструктор копирования.
Если вы сделаете конструктор копирования доступным (т.е. public
) и распечатайте сообщение, чтобы пометить его вызов, вы увидите, что это сообщение не распечатано DEMO и вызывается только перегруженный оператор преобразования.
Это связано с фактом оптимизации копирования.
Таким образом, дело не в том, что перегруженному оператору преобразования требуется конструктор копирования, а в выражении возврата foo
требуется конструктор копирования, потому что вы возвращаете по значению.
В конце концов, конструктор копирования не будет вызван из-за удаления копии, но все же должен быть доступен.