2012-06-26 12 views
6

recientemente volví a visitar el constructor de copia, operador de asignación, copia idom de intercambio se ve aquí: What is the copy-and-swap idiom? y muchos otros lugares -constructor de copia e implementación operador de asignación opciones -

el vínculo anterior es un excelente post - pero todavía tenía unas cuantas preguntas más - Estas preguntas se responden en un montón de lugares, en stackOverflow y muchos otros sitios, pero no he visto un montón de consistencia -

1 - en caso de tener try-catch alrededor de las áreas donde asignamos la nueva memoria para una copia profunda en la copia contra tructor? (Lo he visto de ambas maneras)

2 - Con respecto a la herencia tanto para el constructor de copias como para el operador de asignación, ¿cuándo deberían llamarse las funciones de la clase base y cuándo deberían ser virtuales?

3 - ¿Es std::copy la mejor manera de duplicar memoria en el constructor de copia? Lo he visto con memcpy, y he visto otros decir memcpy lo peor de la tierra.


Considérese el siguiente ejemplo (Gracias por todos los comentarios), que provocó algunas preguntas adicionales:

4 - ¿Hay que hacer el registro para la asignación de uno mismo? Si es así, donde

5 - Off topic pregunta, pero yo he visto intercambiado utilizado como: std::copy(Other.Data,Other.Data + size,Data); debería ser: std::copy(Other.Data,Other.Data + (size-1),Data); si permuta va desde 'el primer al último' y el elemento 0 se encuentra Other.Data?

6 - ¿Por qué no funciona el constructor comentado (tuve que cambiar el tamaño a mysize) - se supone que esto significa que independientemente del orden en que los escriba, el constructor siempre llamará primero al elemento de asignación?

7 - ¿Algún otro comentario sobre mi implementación? Sé que el código es inútil, pero solo intento ilustrar un punto.

class TBar 
{ 

    public: 

    //Swap Function   
    void swap(TBar &One, TBar &Two) 
    { 
      std::swap(One.b,Two.b); 
      std::swap(One.a,Two.a); 
    } 

    int a; 
    int *b; 


    TBar& operator=(TBar Other) 
    { 
      swap(Other,*this); 
      return (*this); 
    } 

    TBar() : a(0), b(new int) {}    //We Always Allocate the int 

    TBar(TBar const &Other) : a(Other.a), b(new int) 
    { 
      std::copy(Other.b,Other.b,b); 
      *b = 22;            //Just to have something 
    } 

    virtual ~TBar() { delete b;} 
}; 

class TSuperFoo : public TBar 
{ 
    public: 

    int* Data; 
    int size; 

    //Swap Function for copy swap 
    void swap (TSuperFoo &One, TSuperFoo &Two) 
    { 
      std::swap(static_cast<TBar&>(One),static_cast<TBar&>(Two)); 
      std::swap(One.Data,Two.Data); 
      std::swap(One.size,Two.size); 
    } 

    //Default Constructor 
    TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[mysize]) {} 
    //TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[size]) {}    *1 

    //Copy Constructor 
    TSuperFoo(TSuperFoo const &Other) : TBar(Other), size(Other.size), Data(new int[Other.size])  // I need [Other.size]! not sizw 
    { 
      std::copy(Other.Data,Other.Data + size,Data);  // Should this be (size-1) if std::copy is First -> Last? *2 
    } 

    //Assignment Operator 
    TSuperFoo& operator=(TSuperFoo Other) 
    { 
      swap(Other,(*this)); 
      return (*this); 
    } 

    ~TSuperFoo() { delete[] Data;} 

}; 

Respuesta

4
  1. Si asigna la memoria, entonces usted necesita para asegurarse de que se libera en el caso de una excepción de ser lanzado. Puede hacerlo con un try/catch explícito, o puede usar un puntero inteligente como std::unique_ptr para mantener la memoria, que luego se eliminará automáticamente cuando el puntero inteligente se destruya mediante el desenrollado de la pila.

  2. Muy raramente necesita un operador de asignación virtual. Llame al constructor de copia de la clase base en la lista de inicialización de miembros, y al operador de asignación de clase base primero en el operador de asignación derivada si está realizando una asignación de miembro; si está haciendo copiar/intercambiar, entonces no necesita llamar al asignación de clase base en su operador de asignación derivada, siempre que la copia y el intercambio se implementen correctamente.

  3. std::copy funciona con objetos y llamará correctamente a los constructores de copia. Si tiene objetos POD simples, entonces memcpy funcionará igual de bien. Yo iría por std::copy en la mayoría de los casos, aunque --- debería optimizarse a memcpy de todos modos para PODs, y evita la posibilidad de errores si agrega un constructor de copias más tarde.

[Actualizaciones para la pregunta actualizada]

  1. Con copia/intercambio como escrito que no hay necesidad de comprobar para la auto-asignación, y de hecho hay manera de hacerlo --- por el momento ingrese el operador de asignación other es una copia , y no tiene forma de saber cuál fue el objeto de origen. Esto solo significa que la autoasignación aún hará una copia/intercambio.

  2. std::copy toma un par de iteradores (primero, primero + tamaño) como entrada. Esto permite rangos vacíos, y es lo mismo que cualquier algoritmo basado en rango en la biblioteca estándar.

  3. El constructor comentado no funciona porque los miembros se inicializan en el orden en que se declaran, independientemente del orden en la lista de inicializadores de miembros. En consecuencia, Data siempre se inicializa primero. Si la inicialización depende de size, obtendrá un valor duff ya que size aún no se ha inicializado. Si intercambia las declaraciones de size y data, este constructor funcionará bien. Los buenos compiladores advertirán sobre el orden de inicialización del miembro que no coincide con el orden de las declaraciones.

+0

Gracias - Actualizado con el ejemplo – MikeyG

1
  1. Si el constructor de lo que está copiado profunda puede tirar algo que puede manejar, seguir adelante y atraparlo. Sin embargo, me gustaría dejar propagar las excepciones de asignación de memoria .
  2. Los constructores de copia (o cualquier constructor) no pueden ser virtuales. Incluya un inicializador de clase base para estos. Los operadores de asignación de copia deben delegar a la clase base, incluso si son virtuales.
  3. memcpy() tiene un nivel demasiado bajo para copiar tipos de clase en C++ y puede generar un comportamiento indefinido. Creo que std::copy suele ser una mejor opción.
1
  1. try-catch se puede utilizar cuando se tiene que deshacer algo. De lo contrario, simplemente deje que el bad_alloc se propague a la persona que llama.

  2. Llamar al constructor de copia o al operador de asignación de la clase base es la forma estándar de dejar que se maneje su copia. Nunca he visto un caso de uso para un operador de asignación virtual, así que supongo que son raros.

  3. std::copy tiene la ventaja de que copia objetos de clase correctamente. memcpy es bastante limitado en qué tipos puede manejar.

3

1 - En caso de tener try-catch alrededor de las áreas en las que destinamos la nueva memoria para una copia en profundidad en el constructor de copia?

En general, solo debería detectar una excepción si puede manejarlo. Si tiene una forma de lidiar localmente con una condición de falta de memoria, tómela; de lo contrario, déjalo ir.

No debería volver normalmente desde un constructor si la construcción ha fallado, eso dejaría a la persona que llama con un objeto no válido, y no hay manera de saber que no es válido.

2 - Con respecto a la herencia tanto para el constructor de copias como para el operador de asignaciones, ¿cuándo se deben llamar las funciones de clase base y cuándo deben ser virtuales?

Un constructor no puede ser virtual, ya que las funciones virtuales sólo pueden ser enviados por un objeto, y no hay ningún objeto antes de crearlo. Por lo general, tampoco haría operadores de asignación virtuales; Las clases que se pueden copiar y asignar se suelen tratar como tipos de "valores" no polimórficos.

Por lo general, que se podría llamar el constructor copia de la clase base de la lista initialiser:

Derived(Derived const & other) : Base(other), <derived members> {} 

y si está usando el lenguaje de copiar y de intercambio, a continuación, su operador de asignación no necesitaría preocuparse por la clase base; que serían manejadas por el canje:

void swap(Derived & a, Derived & b) { 
    using namespace std; 
    swap(static_cast<Base&>(a), static_cast<Base&>(b)); 
    // and swap the derived class members too 
} 
Derived & Derived::operator=(Derived other) { 
    swap(*this, other); 
    return *this; 
} 

3 - ¿Es std::copy la mejor manera para la duplicación de memoria en el constructor de copia? Lo he visto con memcopy, y he visto otros dicen memcopy lo peor de la tierra.

Es bastante inusual tratar con memoria sin procesar; por lo general, su clase contiene objetos y, a menudo, los objetos no se pueden copiar correctamente simplemente copiando su memoria. Copia objetos utilizando sus constructores de copia u operadores de asignación, y std::copy usará el operador de asignación para copiar una matriz de objetos (o, más generalmente, una secuencia de objetos).

Si realmente desea, puede usar memcpy para copiar POD (datos antiguos) y arreglos; pero std::copy es menos propenso a errores (ya que no necesita proporcionar el tamaño del objeto), menos frágil (ya que no se romperá si cambia los objetos para que no sean POD) y potencialmente más rápido (dado que el tamaño del objeto y alineación son conocidos en tiempo de compilación).

+0

Gracias - Actualicé la pregunta con un ejemplo y un poco más de discusión, Por favor, eche un vistazo – MikeyG

+1

@MikeyG: Eso es un montón de preguntas; sería mejor que les preguntaras por separado. Brevemente: (4) no necesita manejar la autoasignación si está utilizando copy-and-swap; (5) 'std :: copy' (y en general funciones que toman rangos de iterador) esperan que el iterador" final "sea * pasado * al final de la secuencia; (6) los miembros siempre se inicializan en el orden en que se declaran en la definición de la clase, por lo que 'Datos' se inicializa antes que' tamaño'. –

Cuestiones relacionadas