¿Podemos devolver los objetos que tienen un constructor eliminado / copia / movimiento privado por valor de una función?

En C ++ 03 es imposible devolver un objeto de una clase que tenga un constructor de copia no definido privado por valor:

struct A { A(int x) { ... } private: A(A const&); }; A f() { return A(10); // error! return 10; // error too! } 

Me preguntaba si esta restricción se eliminó en C ++ 11, por lo que es posible escribir funciones que tengan un tipo de devolución de tipo de clase para clases sin constructores utilizados para copiar o mover. Recuerdo que podría ser útil permitir que los llamadores de una función usen el objeto recién devuelto, pero que no puedan copiar el valor y almacenarlo en algún lugar.

Así es como puede funcionar

 A f() { return { 10 }; } 

Esto funciona a pesar de que A no tiene copia de trabajo o mueve el constructor y ningún otro constructor que pueda copiar o mover una A !

Para hacer uso de esta característica de C ++ 11, el constructor (tomando int en este caso) tiene que ser no explícito.

La restricción no ha sido levantada. Según el especificador de acceso, hay una nota en §12.8 / 32 que explica:

la resolución de sobrecarga de dos etapas se debe realizar independientemente de si se producirá elisión de copia. Determina el nombre del constructor que se invocará si no se realiza una elisión, y el constructor seleccionado debe estar accesible incluso si se elimina la llamada.

A partir de los constructores eliminados de copiar / mover §8.4.3 / 2 establece que

Un progtwig que se refiere a una función eliminada implícita o explícitamente, que no sea para declararlo, está mal formado. [Nota: Esto incluye llamar a la función implícita o explícitamente y formar un puntero o puntero a miembro a la función. Se aplica incluso para referencias en expresiones que no son potencialmente evaluadas. Si una función está sobrecargada, se hace referencia solo si la función se selecciona mediante resolución de sobrecarga. – nota final]

No estoy seguro acerca de este caso particular, pero mi entendimiento de la cita es que, si después de la resolución de sobrecarga en §12.8 / 32 el copiador / movimiento eliminado se selecciona, incluso si la operación se elimina, eso podría constituir una referencia a la función , y el progtwig estaría mal formado.

El código anterior todavía está mal formado en C ++ 11. Pero podrías agregar un constructor de movimiento público a A y luego sería legal:

 struct A { A(int x) {} A(A&&); private: A(A const&); }; A f() { return A(10); // Ok! } 

Me preguntaba si esta restricción se eliminó en C ++ 11.

¿Como puede ser? Al devolver algo por valor, está por definición copiando (o moviéndolo). Y aunque C ++ puede permitir que esa copia / movimiento se elimine en ciertas circunstancias, sigue copiando (o moviéndose) según la especificación.

Recuerdo que podría ser útil permitir que los llamadores de una función usen el objeto devuelto, pero que no puedan copiar el valor y almacenarlo en algún lugar.

Sí. Se deshace del constructor / asignación de copia, pero permite que se mueva el valor. std::unique_ptr hace esto.

Puede devolver un unique_ptr por valor. Pero al hacerlo, estás devolviendo un “prvalue”: un temporal que está siendo destruido. Por lo tanto, si tienes una función g como tal:

 std::unique_ptr g() {...} 

Puedes hacerlo:

 std::unique_ptr value = g(); 

Pero no esto :

 std::unique_ptr value1 = g(); std::unique_ptr value2 = g(); value1 = value 2; 

Pero esto es posible:

 std::unique_ptr value = g(); value = g(); 

La segunda línea invoca el operador de asignación de movimiento en el value . Eliminará el puntero anterior y moverá el nuevo puntero hacia él, dejando el valor anterior vacío.

De esta forma, puede asegurarse de que el contenido de cualquier unique_ptr solo se almacene en un solo lugar. No puede evitar que unique_ptr referencia a él en varios lugares (a través de punteros a unique_ptr o lo que sea), pero habrá como máximo una ubicación en la memoria donde se almacena el puntero real.

Al eliminar los constructores de copiar y mover, se crea un objeto inmóvil . Donde se crea es donde permanecen sus valores, para siempre . El movimiento le permite tener una propiedad única, pero sin estar inmóvil.

Probablemente puedas hackear un proxy para hacer el truco si realmente lo deseas, y tener un constructor de conversión que copie el valor almacenado en el proxy.

Algo como:

 template struct ReturnProxy { //This could be made private, provided appropriate frienship is granted ReturnProxy(T* p_) : p(p_) { } ReturnProxy(ReturnProxy&&) = default; private: //don't want these Proxies sticking around... ReturnProxy(const ReturnProxy&) = delete; void operator =(const ReturnProxy&) = delete; void operator =(ReturnProxy&&) = delete; struct SUPER_FRIENDS { typedef T GO; }; friend struct SUPER_FRIENDS::GO; unique_ptr p; }; struct Object { Object() : data(0) { } //Pseudo-copy constructor Object(ReturnProxy&& proxy) : data(proxy.p ? proxy.p->data : throw "Don't get sneaky with me \\glare") { //steals `proxy.p` so that there isn't a second copy of this object floating around //shouldn't be necessary, but some men just want to watch the world burn. unique_ptr thief(std::move(proxy.p)); } private: int data; Object(const Object&) = delete; void operator =(const Object&) = delete; }; ReturnProxy func() { return ReturnProxy(new Object); } int main() { Object o(func()); } 

Sin embargo, probablemente puedas hacer lo mismo en 03, usando auto_ptr s. Y obviamente no impide el almacenamiento del Object resultante, aunque lo limita a una copia por instancia.