c++ - Mover semántica y evaluación de orden de función
c++11 initialization (3)
Como alternativa a la respuesta de Praetorian, puede usar el delegado de constructor:
class C : public B {
public:
C(std::unique_ptr<A> a) :
C(a->x, std::move(a)) // this move doesn''t nullify a.
{}
private:
C(int x, std::unique_ptr<A>&& a) :
B(x, std::move(a)) // this one does, but we already have copied x
{}
};
Supongamos que tengo lo siguiente:
#include <memory>
struct A { int x; };
class B {
B(int x, std::unique_ptr<A> a);
};
class C : public B {
C(std::unique_ptr<A> a) : B(a->x, std::move(a)) {}
};
Si entiendo las reglas de C ++ sobre "orden no especificado de parámetros de función" correctamente, este código no es seguro. Si el segundo argumento para el constructor de B
se construye primero usando el constructor de movimiento, entonces a
ahora contiene un nullptr
y la expresión a->x
desencadenará un comportamiento indefinido (probablemente segfault). Si el primer argumento se construye primero, entonces todo funcionará según lo previsto.
Si esta fuera una llamada de función normal, podríamos simplemente crear una llamada temporal:
auto x = a->x
B b{x, std::move(a)};
Pero en la lista de inicialización de clases no tenemos la libertad de crear variables temporales.
Supongamos que no puedo cambiar B
, ¿hay alguna forma posible de lograr lo anterior? ¿Desreferenciando y moviendo un unique_ptr
en la misma expresión de llamada de función sin crear un temporal?
¿Qué pasaría si pudieras cambiar el constructor de B
pero no agregar nuevos métodos como setX(int)
? ¿Eso ayudaría?
Gracias
La sugerencia de Praetorian de usar la inicialización de listas parece funcionar, pero tiene algunos problemas:
- Si el argumento unique_ptr es lo primero, no tenemos suerte
- Es demasiado fácil para los clientes de
B
olvidarse accidentalmente de usar{}
lugar de()
. Los diseñadores de la interfaz deB
han impuesto este error potencial.
Si pudiéramos cambiar B, entonces quizás una mejor solución para los constructores sea pasar siempre unique_ptr por referencia de valor real en lugar de por valor.
struct A { int x; };
class B {
B(std::unique_ptr<A>&& a, int x) : _x(x), _a(std::move(a)) {}
};
Ahora podemos usar de forma segura std :: move ().
B b(std::move(a), a->x);
B b{std::move(a), a->x};
Use la inicialización de la lista para construir B
Luego se garantiza que los elementos se evaluarán de izquierda a derecha.
C(std::unique_ptr<A> a) : B{a->x, std::move(a)} {}
// ^ ^ - braces
De §8.5.4 / 4 [dcl.init.list]
Dentro de la lista de inicialización de una lista inicial arrinconada , las cláusulas inicializadoras , incluidas las que resultan de las expansiones de paquetes (14.5.3), se evalúan en el orden en que aparecen. Es decir, cada cálculo de valor y efecto secundario asociado con una cláusula de inicializador dada se secuencia antes de cada cálculo de valor y efecto secundario asociado con cualquier cláusula de inicializador que lo sigue en la lista separada por comas de la lista de inicializadores .