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