c++ copy-constructor implicit-conversion copy-elision

c++ - ¿Se requiere un constructor de copia cuando se devuelve por conversión implícita?



copy-constructor implicit-conversion (3)

El siguiente código compila bien en Visual C ++ 2013, pero no bajo GCC o Clang.

¿Cual es correcta?
¿Se requiere un constructor de copia accesible cuando se devuelve un objeto a través de una conversión implícita?

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; }

Sonido metálico:

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 &); ^


Cuando return una expresión, se crea un objeto temporal del tipo de retorno, se inicializa con esa expresión y luego se mueve (o se copia, si el movimiento no es una opción), en el valor de retorno. Así que necesitas una copia accesible o mover constructor.

Sin embargo, es posible inicializar el valor de retorno directamente, utilizando una lista de arriostramiento. Así que las siguientes obras:

Noncopyable foo() { return {0}; }

Caso similar en vivo ejemplo .


12.8 Copiar y mover objetos de clase [class.copy]

1 / Un objeto de clase se puede copiar o mover de dos maneras: mediante la inicialización (12.1, 8.5), incluso para el paso de argumento de función (5.2.2) y para el retorno de valor de función (6.6.3); [...]

En 6.6.3 La declaración de retorno [stmt.return] :

2 / [...] El valor de la expresión se convierte implícitamente al tipo de retorno de la función en la que aparece. Una declaración de devolución puede implicar la construcción y copia o movimiento de un objeto temporal (12.2) [...]

y 12.2 Objetos temporales [class.temporary] :

1 / Los temporales de tipo de clase se crean en diversos contextos: vinculando una referencia a un prvalue (8.5.3), devolviendo un prvalue (6.6.3), una conversión que crea un prvalue (4.1, 5.2.9, 5.2.11, 5.4) , [...] Nota: incluso si no hay una llamada al destructor o al constructor de copia / movimiento, todas las restricciones semánticas, como la accesibilidad (Cláusula 11) y si la función está eliminada (8.4.3), deberán estar satisfecho. [...]

Argumentaría que GCC y el sonido son correctos. Incluso diría que cada vez que devuelves por valor, el tipo de devolución debe tener una copia accesible o un constructor de movimiento.

La lógica sería que se crea un temporal para convertir el tipo original al nuevo tipo ( int a Noncopyable ), y luego se hace una copia de ese temporal para regresar a la función.

Es esencialmente lo mismo que:

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

¿Esperarías que se necesitara una copia allí? Ciertamente lo haría.


  • La función foo devuelve un objeto no Noncopyable por valor. Por lo tanto, en teoría, un constructor de copia debe ser evocado.

  • Si hace que el constructor de copias esté disponible (es decir, public ) e imprima un mensaje para indicar su evocación, verá que este mensaje no se imprime en la DEMO y solo se evoca al operador de conversión sobrecargado.

  • Esto se debe a la optimización de la copia de la elision.

  • Por lo tanto, no es que el operador de conversión sobrecargado requiera un constructor de copia, sino que la declaración de retorno de foo requiere un constructor de copia porque lo devuelve por valor.

  • Eventualmente, el constructor de copia no será evocado debido a la elección de la copia, pero aún debe estar disponible.