2012-01-14 17 views
7

estoy usando estas dos clasesC++, cómo copiar correctamente std :: vector <Class *> en copy constructor?

// This is generic data structure containing some binary data 
class A { 
public: 
    A(); 
    A(const A&); 
    ~A(); 
} 

// Main data container 
class B { 
public: 
    B(); 
    B(const B&); 
    ~B(); 
protected: 
    std::vector<A *> data; 
} 

// Copy constructor for class b 
B::B(const B& orig):data() { 
    for(std::vector<A *>::const_iterator it = orig.data.begin(); 
     it < orig.data.end(); ++it){ 
     data.push_back(new A(*(*it))); 
    } 
} 

supongo que esta clase lo haría trabajo, pero estoy encontrando a manera de cómo llegar a la perfección total en este.

Al principio :data() - ¿esta inicialización es necesaria para inicializar el vector vacío correctamente (y es parte de escribir un código bueno y limpio)?

Cómo usar vector::iterator en el constructor de copias, la única forma que encontré es la que he escrito en el código (const debe ser obligatorio para el constructor de copias).

¿Copiar solo vector copiará solo los valores del puntero y no los objetos completos?

Y, finalmente, la inicialización de datos nuevos ... ¿Hay alguna manera de cómo puedo reemplazar todo el bucle con una pieza más pequeña de código y/o hay algún estándar sobre cómo escribir el constructor de copia para std :: containers que contiene punteros a objetos?

Sub pregunta: ¿Asumo utilizando vector<A *> es mucho más conveniente y eficaz por varias razones que sólo vector<A> (no copiar cada vez, poder para decidir si (no) para copiar objetos ...)

+0

Quizás quiso decir "Sub pregunta: supongo que estoy usando un vector de punteros ..." –

+0

Preasignación de' datos' en la lista de inicialización. Usar 'push_back()' como ese es muy ineficaz. – lapk

+0

Subrespuesta: Creo que si no puedes usar punteros, no deberías usarlos. – Lol4t0

Respuesta

9
El

data() no es necesario porque eso se hará automáticamente en el vector antes de que se ingrese el constructor. Solo necesita inicializar miembros que sean tipos POD o tipos que no tengan un constructor predeterminado (o referencias, constantes, etc.).

Puede inicializar el vector con la cantidad de elementos que tiene el otro, de modo que el vector no tenga que cambiar su tamaño a medida que crece. Si no lo hace, está comenzando con un vector pequeño y haciéndolo llegar gradualmente al tamaño de destino a través de asignaciones y reasignaciones. Esto hará que el vector del tamaño correcto desde el principio:

B::B(const B& orig) : data(orig.data.size()) { 
    for (std::size_t i = 0; i < orig.data.size(); ++i) 
     data[i] = new A(*orig.data[i]); 
} 

en cuenta que no está utilizando push_back más porque el vector ya está lleno de orig.data.size() número de elementos que son por defecto construida (que es NULL en el caso de punteros).

Esto también recorta el código porque puede usar un número entero para iterarlo en lugar de un iterador.

Si realmente desea utilizar iteradores, se puede hacer

B::B(const B& orig) : data(orig.data.size()) { 
    // auto is preferable here but I don't know if your compiler supports it 
    vector<A*>::iterator thisit = data.begin(); 
    vector<A*>::const_iterator thatit = orig.data.cbegin(); 

    for (; thatit != orig.data.cend(); ++thisit, ++thatit) 
     *thisit = new A(**thatit); 
} 

La ventaja de esto es que va a trabajar con otros tipos de contenedores (como list) con sólo cambiar los tipos de los iteradores (pero de Por supuesto, eso desaparecería si tiene auto).

Si desea agregar una excepción a la seguridad, es necesario un bloque try/catch:

B::B(const B& orig) : data(orig.data.size()) { 
    try { 
     // auto is preferable here but I don't know if your compiler supports it 
     vector<A*>::iterator thisit = data.begin(); 
     vector<A*>::const_iterator thatit = orig.data.cbegin(); 

     for (; thatit != orig.data.cend(); ++thisit, ++thatit) 
      *thisit = new A(**thatit); 
    } catch (...) { 
     for (vector<A*>::iterator i = data.begin(); i != data.end(); ++i) 
      if (!*i) 
       break; 
      else 
       delete *i; 

     throw; 
    } 
} 

De esta manera no tendrá una pérdida de memoria si una de las llamadas new se produce una excepción. Por supuesto, puede usar el try/catch junto con el camino sin iteradores si prefiere hacerlo de esa manera.

+1

Debe eliminar la referencia' orig. datos [i] '. – someguy

+0

@someguy whoops, tienes razón, corregido –

+2

Una cosa más a tener en cuenta es el manejo de errores: ¿qué sucede si uno de los' nuevos 'fai l en el medio del ciclo? Probablemente el programa simplemente termine, pero si el error de falta de memoria se maneja más arriba en la pila de llamadas, tendrá una pérdida de memoria. –

Cuestiones relacionadas