2011-04-16 10 views
5

¿Es esta clase de diseño del C++ estándar 0x manera de prevenir la copia y asignan, para proteger contra el código de cliente accidental doble eliminación de data?Poner objetos no copiable en STD-contenedores

struct DataHolder { 
    int *data; // dangerous resource 
    DataHolder(const char* fn); // load from file or so 
    DataHolder(const char* fn, size_t len); // *from answers: added* 
    ~DataHolder() { delete[] data; } 

    // prevent copy, to prevent double-deletion 
    DataHolder(const DataHolder&) = delete; 
    DataHolder& operator=(const DataHolder&) = delete; 

    // enable stealing 
    DataHolder(DataHolder &&other) { 
    data=other.data; other.data=nullptr; 
    } 
    DataHolder& operator=(DataHolder &&other) { 
    if(&other!=this) { data = other.data; other.data=nullptr}; 
    return *this; 
    } 
}; 

te fijas, que he definido el nuevo movimiento y movimiento-asignar métodos aquí. ¿Los implementé correctamente?

¿Hay alguna manera de que puede - con el movimiento y movimiento-asignar las definiciones - poner DataHolder en un contenedor estándar? como un vector? ¿Cómo haría eso?

Me pregunto, algunas opciones vienen a la mente:

// init-list. do they copy? or do they move? 
// *from answers: compile-error, init-list is const, can nor move from there* 
vector<DataHolder> abc { DataHolder("a"), DataHolder("b"), DataHolder("c") }; 

// pushing temp-objects. 
vector<DataHolder> xyz; 
xyz.push_back(DataHolder("x")); 
// *from answers: emplace uses perfect argument forwarding* 
xyz.emplace_back("z", 1); 

// pushing a regular object, probably copies, right? 
DataHolder y("y"); 
xyz.push_back(y); // *from anwers: this copies, thus compile error.* 

// pushing a regular object, explicit stealing? 
xyz.push_back(move(y)); 

// or is this what emplace is for? 
xyz.emplace_back(y); // *from answers: works, but nonsense here* 

El emplace_back idea es sólo una suposición, aquí.

Editar: He trabajado las respuestas en el código de ejemplo, para la conveniencia de los lectores.

+0

const RValue references? –

Respuesta

6

Su código de ejemplo parece en su mayoría correcto.

  1. constDataHolder &&other (en dos lugares).

  2. if(&other!=this) en su operador de asignación de movimiento parece innecesario pero inofensivo.

  3. El constructor del vector de la lista de inicializadores no funcionará. Esto intentará copiar tu DataHolder y deberías obtener un error de tiempo de compilación.

  4. Las llamadas push_back y emplace_back con argumentos rvalue funcionarán. Aquellos con argumentos lvalue (usando y) le darán errores de tiempo de compilación.

Realmente no hay diferencia entre push_back y emplace_back en la forma en que los ha utilizado. emplace_back es para cuando no desea construir un DataHolder fuera del vector, sino que pasa los argumentos para construir uno solo dentro del vector. P.ej.:

// Imagine this new constructor: 
DataHolder(const char* fn, size_t len); 

xyz.emplace_back("data", 4); // ok 
xyz.push_back("data", 4); // compile time error 

Actualización:

Acabo de notar que su operador de asignación movimiento tiene una pérdida de memoria en el mismo.

DataHolder& operator=(DataHolder &&other) 
{ 
    if(&other!=this) 
    { 
     delete[] data; // insert this 
     data = other.data; 
     other.data=nullptr; 
    } 
    return *this; 
} 
+1

No creo que prevenir el movimiento hacia uno mismo sea inútil. Después de todo, 'a = std :: move (a)' es válido y debe ser no-op. – ltjax

+0

(1) copiar y pegar-error. lo edité. (2) ¿en serio? ¿No es ese el patrón habitual de copia? temp-object no puede ser 'esto'? hmmm. (3) a la derecha, los miembros de la lista de inicio se comportan 'const'. De acuerdo. (4) Ah, eso es "emplace" ... por supuesto. Ahora entiendo. (*) Hermoso todas esas cosas nuevas trabajando juntas: rvalue-refs, perfect-forwarding, template-varargs. ¡Estupendo! – towi

+3

@towi - El patrón habitual para copiar es el modismo de "copiar y cambiar": 'T & operator = (T rhs/* pasa por valor! * /) {This-> swap (rhs); devuelve * esto; } 'Esto tiene tres ventajas: 1) delegados para copiar el ctor, por lo que no es necesario duplicar el código; 2) es seguro para excepciones si el intercambio no arroja (¡y no debería!); y 3) la autoasignación funciona sin una verificación explícita. 'this-> swap (rhs)' (con 'T && rhs') también suele ser una buena forma de implementar el operador de asignación de movimiento. – JohannesD

1

Objetos temporales que no tienen nombre, p. Ej. DataHolder("a"), se mueven cuando están disponibles. El contenedor estándar en C++ 0x siempre se moverá cuando sea posible, eso es lo que también permite que std::unique_ptr se coloque en un contenedor estándar.
Además de eso, ha implementado sus operaciones de movimiento equivocado:

// enable stealing 
    DataHolder(const DataHolder &&other) { 
    data=other.data; other.data=nullptr; 
    } 
    DataHolder& operator=(const DataHolder&&other) { 
    if(&other!=this) { data = other.data; other.data=nullptr}; 
    return *this; 
    } 

¿Cómo se mueve de un objeto constante? No puede simplemente cambiar el otherdata, ya que other es constante. Cambie eso al simple DataHolder&&.

+0

ups, copiar y pegar-error con 'const &&' ies. Quité los consts para que los lectores no aprendan este código defectuoso. – towi

Cuestiones relacionadas