2010-08-11 7 views
12

Estoy buscando la mejor práctica para tratar con objetos no copiables.Cómo tratar objetos no procesables al insertar en contenedores en C++

Tengo una clase mutex, que obviamente no debería ser copiable. Agregué un constructor de copia privada para hacer cumplir eso.

Eso rompió el código - algunos lugares simplemente necesitaban ser reparados, pero tengo un problema genérico donde una clase, usando el mutex como miembro de datos, o por herencia, se está insertando en un contenedor.

Esto suele ocurrir durante la inicialización del contenedor, por lo que el mutex no se ha inicializado todavía, y por lo tanto está bien, pero sin un constructor de copia no funciona. No es aceptable cambiar los contenedores para contener punteros.

¿Algún consejo?

+5

Las preguntas que dicen que "X no es aceptable" no son aceptables. –

+1

¿Alguna sugerencia o solo punteros sin formato? ¿Qué tal shared_ptrs? – SCFrench

Respuesta

4

Si un objeto no se puede copiar, generalmente hay una buena razón. Y si hay una buena razón, entonces no debes subvertir eso poniéndolo en un contenedor que intente copiarlo.

+5

Este es un buen consejo general, así que no te estoy bajando la votación. Pero tampoco responde, así que estoy muy tentado de hacerlo. – Omnifarious

+5

De hecho, es una respuesta. Lo que DBBD ha pedido es incorrecto. Visage le dice lo que está mal: afirmar que los objetos no se pueden copiar, pero usarlos en un contenedor que los copia. Esto no tiene sentido. Además, DBBD ni siquiera dice por qué el uso de punteros no es aceptable, mientras que ES el modo en que debería ser (lo más probable). –

+1

@Omnifarious: Algunas personas hacen preguntas incorrectas. Visage es en mi humilde opinión correcta. – wilx

2

Los contenedores STL dependen en gran medida de que sus contenidos se puedan copiar, por lo tanto, conviértalos o no los coloque en un contenedor.

4

Si no son copiables, el contenedor tiene que almacenar punteros (inteligentes) a esos objetos, o envoltorios de referencia, etc., aunque con C++ 0x, los objetos no copiables aún se pueden mover (como hilos de refuerzo), por lo que pueden almacenarse en contenedores tal como están.

a dar ejemplos: envoltura de referencia (aka impulsar :: ref, un puntero bajo el capó)

#include <vector> 
#include <tr1/functional> 
struct Noncopy { 
private: 
     Noncopy(const Noncopy&) {} 
public: 
     Noncopy() {} 
}; 
int main() 
{ 
     std::vector<std::tr1::reference_wrapper<Noncopy> > v; 
     Noncopy m; 
     v.push_back(std::tr1::reference_wrapper<Noncopy>(m)); 
} 

C++ 0x, probado con gcc:

#include <vector> 
struct Movable { 
private: 
     Movable(const Movable&) = delete; 
public: 
     Movable() {} 
     Movable(Movable&&) {} 
}; 
int main() 
{ 
     std::vector<Movable> v; 
     Movable m; 
     v.emplace_back(std::move(m)); 
} 

EDIT: No importa, C++ 0x FCD dice, en 30.4.1/3,

Un tipo Mutex no se puede copiar ni mover.

Así que es mejor con los consejos para ellos. Inteligente o envuelto de otra manera según sea necesario.

+0

Para la mayoría de los contenedores C++ 0x, los objetos ni siquiera tienen que ser móviles; 'emplace' puede construirlos en su lugar. Deben ser móviles para 'vector' y' deque', ya que estos necesitan reasignar memoria a medida que crecen. –

+1

Además, la forma aceptada por C++ 0x de eliminar el constructor de copia es 'Moveable (const Movable &) = delete;'. – Omnifarious

+0

Esto es realmente interesante, pero sospecho que si se mueve un mutex mientras se mantiene, algo muy malo podría suceder con algunas implementaciones. – Omnifarious

1

La mejor opción es utilizar punteros o algún tipo de clase de contenedor que utilice punteros debajo del capó. Eso permitiría que estos sean copiados sanamente, y en realidad hacer lo que se espera que haga una copia (compartir el mutex).

Pero, como dijo que no hay punteros, hay otra opción. Parece que los mensajes Mutex son "a veces reproducibles", quizás debería escribir un constructor de copia y un operador de asignación y hacer que estos emitan una excepción si alguna vez se copia un mutex después de que se haya inicializado. El lado negativo es que no hay manera de saber que lo estás haciendo mal hasta el tiempo de ejecución.

11

tres soluciones aquí:

1. utilizar punteros - La solución rápida es hacer que sea un recipiente de punteros - por ejemplo, a shared_ptr.

Esa sería la "buena" solución si sus objetos no se pueden copiar, y no es posible utilizar otros contenedores.

2. Otros contenedores - Alternativamente, podría usar contenedores que no sean de copia (que usan construcción en el lugar), sin embargo, no son muy comunes y en gran medida incompatibles con STL. (Lo he intentado por un tiempo, pero simplemente no es bueno)

Esa sería la solución "divina" si tus objetos no se pueden copiar y no es posible utilizar punteros.

[edit] Con C++ 13, std :: vector permite la construcción inplace (emplace_back), y se puede usar para objetos no copiables que sí implementan la semántica de movimiento. [/ editar]

3. Fijar el copiabilidad - Si la clase es copiable como tal, y el mutex no es así, "simplemente" debe arreglar el constructor de copia y el operador de asignación.

escribirlos es un dolor, ya que por lo general tiene que copiar & asignar todos los miembros, excepto la exclusión mutua, pero que a menudo se puede simplificar:

template <typename TNonCopyable> 
struct NeverCopy : public T 
{ 
    NeverCopy() {} 
    NeverCopy(T const & rhs) {} 

    NeverCopy<T> & operator=(T const & rhs) { return *this; } 
} 

Y cambiando miembro de exclusión mutua a

NeverCopy<Mutex> m_mutex; 

Desafortunadamente, al usar esa plantilla, pierdes constructores especiales de Mutex.

[editar] Advertencia: "Reparar" la Copia CTor/asignment a menudo requiere que bloquee el lado derecho en la construcción de copia, y asegure ambos lados en la asignación. Desafortunadamente, no hay forma de anular la copia del ctor/asignación y llamar a la implementación predeterminada, por lo que el truco NeverCopy podría no funcionar para usted sin un bloqueo externo. (Hay algunas otras soluciones con sus propias limitaciones.)

0

Usar un mutex en una clase no significa necesariamente que la clase no deba copiarse. Puede (casi) siempre implementarlo como esto:

C::C (C const & c) 
// No ctor-initializer here. 
{ 
    MutexLock guard (c.mutex); 

    // Do the copy-construction here. 
    x = c.x; 
} 

Si bien esto hace que sea algo posible copiar las clases con el mutex, que probablemente no debería hacerlo. Lo más probable es que su diseño sea mejor sin mutex por instancia.

1

Utilice punteros inteligentes como boost :: shared_ptr o use otros contenedores, como boost :: intrusive. Ambos requerirán modificar su código, a través de.

2

No hay una respuesta real a la pregunta dada cómo lo ha enmarcado. No hay forma de hacer lo que quieras. La respuesta real es que el contenedor contenga punteros, y usted ha dicho que no está bien por algún motivo no especificado.

Algunos han hablado acerca de cosas que se pueden mover y el uso de C++ 0x en el que los contenedores a menudo requieren que sus elementos se puedan mover, pero no requieren que se puedan copiar. Creo que esta es una solución pobre también porque sospecho que los mutexes no deberían moverse mientras están enganchados, y esto hace que sea imposible moverlos.

Por lo tanto, la única respuesta real restante es apuntar a los mutexes. Use ::std::tr1::shared_ptr (en #include <tr1/memory>) o ::boost::shared_ptr para señalar los mutexes. Esto requiere cambiar las definiciones de las clases que tienen los mutexes dentro de ellos, pero parece que lo estás haciendo de todos modos.

+0

Mover un objeto como un mutex es una cuestión de mover algunos punteros proporcionados por el sistema operativo, nada Más. No puede mover un mutex real, pero es trivial mover el control proporcionado por el sistema operativo. – Puppy

+0

@DeadMG - Si el mutex se mantiene o contendió, eso probablemente significa que hay punteros 'this' apuntando a la ubicación del objeto original. Además, es posible que el sistema operativo identifique el mutex por su dirección de memoria. – Omnifarious

0

Usando C++ 11 en Ubuntu 14.04 (que incluye emplace_back), he conseguido que esto funcione.

me encontré con que emplace_back funcionó bien, pero borrado (y probablemente inserción) no funcionó porque, cuando el vector se barajando los elementos a lo largo de llenar la brecha, que mas globalizado usando:

*previous = *current; 

encontré el truco estaba permitiendo la asignación movimiento en mi clase de recurso:

Watch& operator=(Watch &&other); 

Esta es mi inotify_watch clase, que puede vivir en un std :: vector:

class Watch { 
private: 
    int inotify_handle = 0; 
    int handle = -1; 
    // Erases all knowledge of our resources 
    void erase() { 
    inotify_handle = 0; 
    handle = -1; 
    } 
public: 
    Watch(int inotify_handle, const char *path, uint32_t mask) 
     : inotify_handle(inotify_handle), 
     handle(inotify_add_watch(inotify_handle, path, mask)) { 
    if (handle == -1) 
     throw std::system_error(errno, std::system_category()); 
    } 
    Watch(const Watch& other) = delete; // Can't copy it, it's a real resource 
    // Move is fine 
    Watch(Watch &&other) 
     : inotify_handle(other.inotify_handle), handle(other.handle) { 
    other.erase(); // Make the other one forget about our resources, so that 
        // when the destructor is called, it won't try to free them, 
        // as we own them now 
    } 
    // Move assignment is fine 
    Watch &operator=(Watch &&other) { 
    inotify_handle = other.inotify_handle; 
    handle = other.handle; 
    other.erase(); // Make the other one forget about our resources, so that 
        // when the destructor is called, it won't try to free them, 
        // as we own them now 
    return *this; 
    } 
    bool operator ==(const Watch& other) { 
    return (inotify_handle == other.inotify_handle) && (handle == other.handle); 
    } 
    ~Watch() { 
    if (handle != -1) { 
     int result = inotify_rm_watch(inotify_handle, handle); 
     if (result == -1) 
     throw std::system_error(errno, std::system_category()); 
    } 
    } 
}; 
Cuestiones relacionadas