resueltos resolucion operador objetos metodos herencia ejercicios constructores codigo clases ambito c++ return-value c++17 deleted-functions

objetos - operador de resolucion de ambito c++



¿La clase con todos los constructores/operadores generados automáticamente eliminados todavía puede devolverse desde una función? (2)

Recientemente, encontré esta answer que describe cómo inicializar una serie std::array de elementos no construibles por defecto. No estaba tan sorprendido porque esa respuesta claramente no hace ninguna construcción por defecto.

En su lugar, está construyendo un std::array temporal utilizando la inicialización agregada, luego se mueve (si el constructor de movimiento está disponible) o se copia en la variable nombrada cuando la función regresa. Así que solo necesitamos que el constructor de movimiento o el constructor de copia estén disponibles.

O eso pensé...

Luego vino este pedazo de código que me confundió:

struct foo { int x; foo(int x) : x(x) {} foo() = delete; foo(const foo&) = delete; foo& operator=(const foo&) = delete; foo(foo&&) = delete; foo& operator=(foo&&) = delete; }; foo make_foo(int x) { return foo(x); } int main() { foo f = make_foo(1); foo g(make_foo(2)); }

Los cinco constructores / operadores de miembros especiales se eliminan explícitamente, por lo que ahora no debería poder construir mi objeto a partir de un valor de retorno, ¿correcto?

Incorrecto.

Para mi sorpresa, esto se compila en gcc (con C ++ 17)!

¿Por qué compila esto? Claramente, para devolver un foo desde la función make_foo() , debemos construir un foo . Lo que significa que en la función main() estamos asignando o construyendo un foo partir del foo devuelto. ¡¿Cómo es eso posible?!


Bienvenido al maravilloso mundo de la copia garantizada (nuevo en C ++ 17. Consulte también esta pregunta ).

foo make_foo(int x) { return foo(x); } int main() { foo f = make_foo(1); foo g(make_foo(2)); }

En todos estos casos, está inicializando un valor foo desde un valor predefinido de tipo foo , por lo que simplemente ignoramos todos los objetos intermedios e inicializamos directamente el objeto más externo desde el inicializador real. Esto es exactamente equivalente a:

foo f(1); foo g(2);

Ni siquiera consideramos mover constructores aquí, por lo que el hecho de que se eliminen no importa. La regla específica es [dcl.init]/17.6.1 ; solo después de este punto consideramos a los constructores y realizamos la resolución de sobrecargas.


Tenga en cuenta que antes de C ++ 17 (antes de la elección de copia garantizada), es posible que ya devuelva ese objeto con las listas de iniciación con llaves:

foo make_foo(int x) { return {x}; // Require non explicit foo(int). // Doesn''t copy/move. }

Pero el uso sería diferente:

foo&& f = make_foo(1); foo&& g(make_foo(2));