2011-06-11 10 views
16

Me gusta la idea de las variables miembro de const, especialmente cuando envuelvo las funciones C en las clases. El constructor toma un identificador de recurso (por ejemplo, un descriptor de archivo) que permanece válido durante todo el tiempo de vida del objeto y el destructor finalmente lo cierra. (Esa es la idea detrás de RAII, ¿no?)Mover el constructor y las variables de miembros de la estructura

Pero con el C++ 0x move constructor me encuentro con un problema. Como el destructor también se invoca en el objeto "descargado", necesito evitar la limpieza del identificador del recurso. Dado que la variable miembro es const no tengo manera de asignar el valor -1 o INVALID_HANDLE (o valores equivalentes) para indicar al destructor que no debería hacer nada.

¿Hay alguna manera de que no se llame al destructor si el estado de un objeto se movió a otro objeto?

Ejemplo:

class File 
{ 
public: 
    // Kind of "named constructor" or "static factory method" 
    static File open(const char *fileName, const char *modes) 
    { 
     FILE *handle = fopen(fileName, modes); 
     return File(handle); 
    } 

private: 
    FILE * const handle; 

public: 
    File(FILE *handle) : handle(handle) 
    { 
    } 

    ~File() 
    { 
     fclose(handle); 
    } 

    File(File &&other) : handle(other.handle) 
    { 
     // The compiler should not call the destructor of the "other" 
     // object. 
    } 

    File(const File &other) = delete; 
    File &operator =(const File &other) = delete; 
}; 

Respuesta

7

No, no hay manera de hacer esto. Sugeriría que si realmente está apegado a la variable handle que es const, debe tener una variable de miembro de indicador non-const que indique si la destrucción debería o no hacer algo.

+1

Tu respuesta es, con seguridad, un camino por recorrer. Pero relacionado con C++ 0x, no me gusta el estilo que tienen los destructores para comprobar si la destrucción realmente debería ocurrir. ¿No deberían suponer que el objeto está en pleno funcionamiento y ahora es el punto de destrucción? – mazatwork

+1

@mazatwork: Bueno, piénsalo de esta manera. Supongamos que tiene un objeto complicado que podría estar en varios estados diferentes y cada uno requiere un conjunto diferente de destructores. Como, por ejemplo, hay un caché que puede inicializarse o no, o una conexión de base de datos que puede o no ser cerrada. ¿No estás 'realmente destruyendo' cuando no cierras la conexión de la base de datos que no está abierta en tu destructor? Por supuesto no. Esto es básicamente lo mismo. Todavía estás destruyendo, es solo que el estado en el que se encuentra el objeto no cumple con mucho trabajo. – Omnifarious

+0

¿Por qué no dejar que el constructor de movimientos realice una limpieza (si es realmente necesario) para que el destructor quede con la destrucción real? En mi opinión, esto encajaría mucho mejor. Debido a que hablamos del mismo objeto, una doble destrucción podría no ser razonable. Tu ejemplo con objetos complejos es algo que trato de evitar usando técnicas como RAII y DI. – mazatwork

3

La forma típica de implementar un constructor de movimiento es poner a cero o invalidar los miembros de la instancia que se está moviendo (vea MSDN para un ejemplo simple). Por lo tanto, yo diría que simplemente no use const aquí ya que es incompatible con los objetivos de la semántica de movimientos.

+0

Bueno, puedes usar 'const' y el compilador generará el constructor de movimientos (por supuesto, si [todos los requisitos] (https://stackoverflow.com/a/8285499) están satisfechos). Aquí hay un ejemplo como prueba: [codepad.org/Xh4va2eR](http://codepad.org/Xh4va2eR) (ignore los viejos errores del compilador del teclado ...) –

13

Es por eso que no debe declarar dichas variables miembro const. Las variables de miembro const generalmente no sirven para nada. Si no desea que los usuarios muten al FILE*, no les proporcione funciones para hacerlo, y si quiere evitar mutar por accidente, marque sus funciones const. Sin embargo, no realice las variables de miembro en sí const - porque luego se encuentra con diversión cuando comienza a usar mover o copiar semántica.

+7

Las variables del miembro de Const son útiles como las referencias. A menudo quiere mantener un valor y sabe que no lo va a cambiar, lo que podría permitir algunas optimizaciones por parte del compilador. Marcar los métodos como const solo funcionará siempre que no tenga algunas variables mutables. – mazatwork

+3

Realmente no permite ninguna optimización de compilador útil, en mi experiencia. Y evita muchas semánticas muy útiles. – Puppy

+0

¡Exactamente cierto! Me encontré con ** diversión ** porque declaro miembro de una clase subyacente como miembro de la estafa. – user8385554

-1

El conteo de referencias es un método estándar que resuelve su problema. Considere agregar el recuento de referencias a su clase; ya sea manualmente o usando herramientas existentes como boost shared_ptr.

+2

Este es un tema ortogonal del problema que se plantea (que es el manejo del movimiento de 'Archivos'). Lo que propone es hacer 'shared_ptr ' s. Podría ser apropiado, pero tal vez no, y ciertamente implicaría un cambio de diseño más grande –

0

En realidad, me he encontrado con este problema yo también hoy. No estoy dispuesto a aceptar 'no se puede hacer' 'conteo/uso Referencia shared_ptr' &, googlear más, se me ocurrió con esta clase de base:

class Resource 
{ 
private: 
    mutable bool m_mine; 

protected: 
    Resource() 
    : m_mine(true) 
    { 
    } 

    Resource(const Resource&)  = delete; 
    void operator=(const Resource&) = delete; 

    Resource(const Resource&& other) 
    : m_mine(other.m_mine) 
    { 
     other.m_mine = false; 
    } 

    bool isMine() const 
    { 
     return m_mine; 
    } 
}; 

Todos los métodos y constructores están protegidos, es necesario heredar de usarlo Observe el campo mutable: esto significa que el descendiente puede ser un miembro constante en una clase. Por ejemplo,

class A : protected Resource 
{ 
private: 
    const int m_i; 

public: 
    A() 
    : m_i(0) 
    { 
    } 

    A(const int i) 
    : m_i(i) 
    { 
    } 

    A(const A&& a) 
    : Resource(std::move(a )) 
    , m_i  (std::move(a.m_i)) // this is a move iff member has const move constructor, copy otherwise 
    { 
    } 

    ~A() 
    { 
     if (isMine()) 
     { 
      // Free up resources. Executed only for non-moved objects 
      cout << "A destructed" << endl; 
     } 
    } 
}; 

El campo (s) de A se puede const ahora. Tenga en cuenta que he heredado protegido, para que el usuario no pueda asignar accidentalmente A al recurso (o lo corte con mucho gusto), pero A aún no es final, por lo que aún puede heredar de esto (una razón válida para heredar de un recurso es por ejemplo, para tener acceso separado de lectura y escritura). Este es uno de los casos extremadamente raros cuando la herencia protegida no significa automáticamente que su diseño es defectuoso; Sin embargo, si le resulta difícil de entender, puede usar herencia pública.

Entonces, suponiendo que tiene una struct X:

struct B 
{ 
    const A m_a; 
    const X m_x; 

    B(const A&& a, const X& x) // implement this way only if X has copy constructor; otherwise do for 'x' like we do for 'a' 
    : m_a(std::move(a)) 
    , m_x(   x ) 
    { 
    } 

    B(const B&& b) 
    : m_a(std::move(b.m_a)) 
    , m_x(std::move(b.m_x)) // this is a move iff X has move constructor, copy otherwise 
    { 
    } 

    ~B() 
    { 
     cout << "B destructed" << endl; 
    } 
}; 

Tenga en cuenta que los campos de B también pueden ser const. Nuestros constructores de movimiento son const. Dado que sus tipos tienen constructores de movimiento apropiados, cualquier memoria asignada en el montón se puede compartir entre los objetos.

Cuestiones relacionadas