2010-03-01 40 views
6

Tengo una clase que genera un número arbitrario de objetos de trabajo que calculan sus resultados en std::vector. Voy a eliminar algunos de los objetos del trabajador en ciertos puntos, pero me gustaría mantener sus resultados en un orden determinado, solo conocido por la clase que los spawned. Por lo tanto, proporciono los vectores para la salida en la clase A.iterador frente a referencia frente a puntero

Tengo (IMO) tres opciones: podría tener punteros a los vectores, referencias o iteradores como miembros. Mientras que la opción del iterador tiene ciertas desventajas (el iterador podría incrementarse). No estoy seguro de si los punteros o las referencias son más claros. Creo que las referencias son mejores porque no pueden ser NULL y un cruncher requeriría la presencia de un vector.

Lo que no estoy seguro es la validez de las referencias. ¿Serán invalidados por algunas operaciones en el std::list< std::vector<int> >? ¿Son esas operaciones lo mismo que invalidar los iteradores de std::list? ¿Hay otro enfoque que no veo ahora? También el acoplamiento a un contenedor no se siente bien: forzo un contenedor específico a la clase Cruncher.

Código proporcionado para mayor claridad:

#include <list> 
#include <vector> 
#include <boost/ptr_container/ptr_list.hpp> 

class Cruncher { 
    std::vector<int>* numPointer; 
    std::vector<int>& numRef; 
    std::list< std::vector<int> >::iterator numIterator; 
public: 
    Cruncher(std::vector<int>*); 
    Cruncher(std::vector<int>&); 
    Cruncher(std::list< std::vector<int> >::iterator); 
}; 

class A { 
    std::list< std::vector<int> > container; 
    boost::ptr_list< std::vector<int> > container2; 
    std::vector<Cruncher> cruncherList; 
}; 

Respuesta

6

Si se invalida un iterador, también invalidaría un puntero/referencia que el iterador se convirtió en. Si usted tiene esto:

std::vector<T>::iterator it = ...; 
T *p = &(*it); 
T &r = *p; 

si se invalida el iterador (por ejemplo, una llamada a push_back puede invalidar todos los iteradores de vectores existentes), también serán invalidadas el puntero y la referencia.

Desde el (la capacidad del vector) 23.2.4.2/5 estándar:

Notas: Reasignación invalida todas las referencias, los punteros, y los iteradores que se refieren a los elementos de la secuencia.

El mismo principio general vale para std :: list. Si un iterador es invalidado, los punteros y las referencias en las que se convierte el iterador también se invalidan.

La diferencia entre std :: list y std :: vector es lo que causa la invalidación del iterador. Un iterador std :: list es válido siempre que no elimine el elemento al que se refiere. Entonces, como std::vector<>::push_back puede invalidar un iterador, std::list<>::push_back no puede.

+0

Iba a usar std :: list para almacenar los referentes como se indica en el muestra. Si solo cambia su cita de la norma a la sección que explica la invalidación de las listas, su respuesta está bien. – pmr

+0

@pmr - actualicé mi respuesta para incluir más información sobre listas. –

1

Si los contenidos del vector padre se vuelven a asignar después de generar sus hilos de trabajo, entonces sus punteros, referencias, iteradores, o lo que sea es casi seguro inválido. Una lista PUEDE ser diferente (dado cómo están asignados) pero no sé, e incluso puede depender de la plataforma.

Básicamente, si tiene varios subprocesos de trabajo, probablemente sea más seguro tener un método en la clase primaria para volcar los resultados hasta que la copia no sea tan exigente. Claro que no es tan bueno como asignarlo directamente a los padres, pero luego debes asegurarte de que el contenedor al que estás vertiendo no se "pierda" en la reasignación.

Si se garantiza que la lista que está utilizando no volverá a asignar su "otro" espacio cuando se agreguen (o eliminen) los miembros, eso logrará lo que está buscando, pero el vector definitivamente no es seguro.Pero de cualquier manera, la forma en que acceda (puntero, referencia o iterador) probablemente no importa mucho mientras su "contenedor raíz" no se mueva alrededor de su contenido.

Editar:

Como se mencionó en los comentarios a continuación, aquí está un bloque sobre la lista de SGI's website (el énfasis es mío):

Las listas tienen la importante propiedad de que inserción y empalme no lo hacen invalidar iteradores para listar elementos, y que incluso la eliminación invalida solo los iteradores que apuntan a los elementos que se eliminan. El orden de los iteradores puede ser cambiado (es decir, la lista :: iterador podría tener un predecesor o sucesor diferente después de una operación de la lista de lo que hizo antes), pero los propios iteradores no será invalidado o puestos a apuntan a diferentes elementos a menos que esa invalidación o mutación sea explícita.

Así que esto básicamente dice "use una lista como su tienda principal" y luego cada trabajador puede volcarse por sí mismo, y sabe que no se invalidará cuando otro trabajador termine completamente y su vector se elimine del lista.

+0

Los iteradores de lista siguen siendo válidos siempre que el elemento al que apuntan actualmente no se haya borrado. – James

+0

Tienes razón, Autopulated. El autor puede consultar http://stackoverflow.com/questions/1436020/c-stl-containers-whats-the-difference-between-deque-and-list/1436038#1436038 en el párrafo sobre listas. – fogo

+0

@fogo: gracias, edité mi OP para agregar el texto del sitio de SGI. –

0

En la versión actual de C++ (es decir, sin constructores de movimiento), los punteros en los elementos incrustados en una lista std :: se invalidarán junto con los iteradores de la lista.

Sin embargo, si usó una std :: list *>, entonces el vector * podría moverse, pero el vector no lo haría, por lo que su puntero en el vector seguiría siendo válido.

Con la adición de constructores de movimiento en C++ 0x, es probable que el contenido del vector permanezca inalterado a menos que el propio vector se redimensione, pero dicha suposición sería inherentemente no portátil.

0

Me gusta el parámetro del puntero. Es una cuestión de estilo. Prefiero este estilo de tipo de parámetro:

  • Referencia de Const: Se está leyendo un objeto grande. La referencia evita el despilfarro. Parece justo pasar por valor en el momento de la llamada.
  • Puntero: Se está pasando el objeto para leer y escribir. La llamada tendrá un "&" para obtener el puntero, por lo que la escritura se hace evidente durante la revisión del código.
  • Referencia no constante: prohibida, porque la revisión del código no puede decir qué parámetros pueden modificarse como efecto secundario.

Como dices, un iterador crea una dependencia inútil en el tipo de contenedor padre. (std :: list se implementa como una lista de doble enlace, por lo que solo borrar su entrada invalida un vector. Así que funcionaría)

+1

¿Cuál es la diferencia en términos de efectos secundarios cuando compara referencias no const a punteros? No puedo ver ninguno además de mencionar otro ampersand en un lugar diferente. – pmr

+0

Una referencia no constante se ve exactamente como pasar por valor cuando se llama a la función, pero la función puede cambiar el parámetro. Esto significa que alguien que lea el código tiene que leer toda la documentación de cada función para saber adecuadamente qué función podría cambiar. Con el estilo que describí, puede leer una llamada y saber que los parámetros sin símbolos y no se cambiarán, y los que sí lo tienen. Esto puede ser útil para revisiones de código formales de software grande. –

+1

Si no ha leído la documentación de una función, no debería llamarla. EDITAR: Y seguro como el infierno no debería revisarlo. –

Cuestiones relacionadas