2011-04-05 25 views
9

Estoy peleando con la siguiente propuesta ahora, y quiero saber argumentos legales y en menor grado morales en su contra o por ello.RVO/NRVO y el constructor público de copia no definida

Lo que teníamos:

#include <vector> 

class T; 

class C 
{ 
public: 
    C() { } 
    ~C() { /*something non-trivial: say, calls delete for all elements in v*/ } 
    // a lot of member functions that modify C 
    // a lot of member functions that don't modify C 
private: 
    C(C const &); 
    C& operator=(C const&); 
private: 
    std::vector< T* > v; 
}; 

void init(C& c) { } // cannot be moved inside C 

// ... 
int main() 
{ 
    // bad: two-phase initialization exposed to the clients 
    C c; 
    init(c); 

    // bad: here follows a lot of code that only wants read-only access to c 
    //  but c cannot be declared const 
} 

Lo que se ha propuesto:

#include <vector> 

class T; 

class C 
{ 
public: 
    C() { } 
    ~C() { /*calls delete for all elements in v*/ } 

    // MADE PUBLIC 
    C(C const &); // <-- NOT DEFINED 

    // a lot of member functions that modify C 
    // a lot of member functions that don't modify C 
private: 
    C& operator=(C const&); 
private: 
    vector< T* > v; 
}; 

C init() // for whatever reason object CANNOT be allocated in free memory 
{ 
    C c; 
    // init c 
    return c; 
} 

// ... 
int main() 
{ 
    C const & c = init(); 
} 

Esto compila y enlaces (y obras) utilizando reciente g ++ (que es el único compilador objetivo) tanto 4.1.2 y 4.4.5 - debido a (N) RVO, nunca se llama al constructor de copias; destructor se llama al final de main() solamente.

Se afirma que la técnica es perfectamente correcta, porque no hay forma de que el constructor de copias pueda ser mal utilizado (si alguna vez se ha generado sería un error del enlazador), y hacerlo público evita que el compilador se queje privado.

Me parece realmente incorrecto usar ese truco, que creo que contradice el espíritu de C++ y se parece más al pirateo, en el mal sentido de la palabra.

Mis sentimientos no son argumentos suficientes, entonces estoy buscando tecnicismos ahora.

Por favor, no publicar los libros de texto C++ cosas aquí:

  • Soy consciente de "la regla de tres" y leído a través de 12,8/15 y 12.2 de la Santa estándar; No puedo usar vector<shared_ptr<T> > ni ptr_vector<T>;
  • No puedo asignar C en la memoria libre y devolverlo de init a través de C*.

Gracias.

+1

Supongo que la construcción de un movimiento no es una posibilidad para usted? –

+0

@Konrad Rudolph: desafortunadamente no (g ++ 4.1.2 es uno de los compiladores que deberíamos soportar, por lo tanto, no hay características 0x). –

+5

"hacerlo público evita que el compilador estúpido se queje de uno privado". - No es un compilador estúpido. El estándar requiere que el constructor de copia sea accesible incluso si se elimina la copia, porque la elisión de copia es opcional. Por lo tanto, si se permite que sea privado, un programa que se base en la elisión para evitar un error no se conformará estrictamente de todos modos, y en este caso el estándar requiere que se lo diagnostique. El compilador te está ayudando a escribir un código portátil, admitiéndolo en contra de tus deseos ;-) –

Respuesta

10

Esto compila y enlaces (y obras) utilizando reciente g ++ (que es el único compilador objetivo) tanto en 4.1.2 y 4.4.5 - a causa de (N) RVO el constructor de copia no es nunca llamado; destructor se llama al final de main() solamente.

Si bien puede funcionar con GCC, su código realmente tiene un comportamiento indefinido porque hace referencia a una función que no está definida. En tal caso, su programa está mal formado; no se requiere diagnóstico. Lo que significa que GCC puede ignorar la violación de las reglas, pero otros compiladores pueden diagnosticarlo o hacer algo extraño.

Por lo tanto, en ese sentido, lo rechazaría de esta manera.

Mis sentimientos no son argumentos suficientes, entonces estoy buscando tecnicismos ahora.

Quiere tener semántica de movimiento aquí. ¿Qué tal tener esto explícito?

class T; 
class C; 

struct CMover { 
    C *c; 
private: 
    CMover(C *c):c(c) { } 
    friend CMover move(C &c); 
}; 

class C { 
public: 
    C() { } 
    ~C() { /*calls delete for all elements in v*/ } 

    C(CMover cmove) { 
     swap(v, cmove.c->v); 
    } 

    inline operator CMover(); 

    // a lot of member functions that modify C 
    // a lot of member functions that don't modify C 
private: 
    C& operator=(C const&); // not copy assignable 
    C(C &); // not lvalue copy-constructible 

private: 
    vector< T* > v; 
}; 

CMover move(C &c) { return CMover(&c); } 
C::operator CMover() { return move(*this); } 

Ahora se puede decir

C init() // for whatever reason object CANNOT be allocated in free memory 
{ 
    C c; 
    return move(c); 
} 

int main() { 
    C const c(init()); 
} 
+0

¿me puede indicar dónde C++ 03 prohibe hacer referencia a las funciones no definidas? Con respecto a la semántica de movimientos, no lo queremos * tan mal * en este caso particular: mi pregunta es más acerca de por qué esta propuesta debe ser aceptada o prohibida. –

+4

@Alex 3.2p3, la propuesta debe rechazarse porque no funciona de manera portátil. Entiendo tu pregunta, pero pensé que también diría cómo la resolvería además de responder a tu pregunta. –

+0

Gracias, 3.2/2 + 3.2/3 muestra claramente que eso es UB. También nos hemos dado cuenta de que MSVC++ 2008 y 2010 (que no son compatibles * ahora *, pero nunca se sabe) rechazan este código. –

0

La respuesta obvia es que el compilador no está obligado a eludir la construcción de copias, especialmente si la optimización está desactivada (aunque g ++ aún elides la copia incluso sin optimización). Por lo tanto, estás restringiendo la portabilidad.

Es muy probable que si se compila en un compilador en particular, funcione como se esperaba.

Sé que hay un montón de restricciones aparentemente arbitrarias/artificiales sobre lo que puede hacer, pero ¿puede utilizar un titular de proxy para C?

class C 
{ 
private: 
    C() { } 
    ~C() { /*calls delete for all elements in v*/ } 

    // ... 
    friend class C_Proxy; 
}; 

class C_Proxy 
{ 
public: 
    C_Proxy() : c_() { init(c_); } 

    // Or create a public const& attribute to point to this. 
    const C& get_C() const { return c_; } 

private: 
    C c_; 
}; 
0

Esto se ve como algo que no conseguirá de cabeza de la gente en razones técnicas solo (también conocido como "Pero compila y funciona en nuestro compilador!"), Así que tal vez un enfoque conceptualmente más simple podría ser una buena idea?

Si su preocupación es la constness ...

C c; 
init(c); 

// bad: here follows a lot of code that only wants read-only access to c 
//  but c cannot be declared const 

vs

C const & c = init(); 

los más simples (como en: las cosas sin trucos y el proxy es necesario) la solución podría ser:

C c_mutable_object; 
init(c_mutable_object); 
C const& c = c_mutable_object; 

// ... lots of code with read-only access to c 
Cuestiones relacionadas