Vida útil de referencia constante de C ++ (adaptador de contenedor)

Tengo un código que se ve así:

class T {}; class container { const T &first, T &second; container(const T&first, const T & second); }; class adapter : T {}; container(adapter(), adapter()); 

Pensé que la vida de referencia constante sería la vida útil del contenedor. Sin embargo, parece que de lo contrario, el objeto adaptador se destruye después de que se crea el contenedor, dejando la referencia colgando.

¿Cuál es el tiempo de vida correcto?

¿es el scope de la stack del objeto temporal del adaptador el scope del objeto del contenedor o del constructor del contenedor?

¿Cómo implementar correctamente el objeto temporal de enlace a la referencia de miembros de la clase?

Gracias

De acuerdo con el estándar C ++ 03, un límite temporal a una referencia tiene diferentes tiempos de vida dependiendo del contexto. En su ejemplo, creo que se aplica la parte resaltada a continuación (12.2 / 5 “Objetos temporales”):

El temporal al que se enlaza la referencia o el temporal que es el objeto completo de un subobjeto del cual el temporal está vinculado persiste durante el tiempo de vida de la referencia, excepto como se especifica a continuación. Un límite temporal a un miembro de referencia en un ctor-initializer (12.6.2) del constructor persiste hasta que el constructor sale. Un límite temporal a un parámetro de referencia en una llamada a función (5.2.2) persiste hasta la finalización de la expresión completa que contiene la llamada.

Por lo tanto, al vincular un temporal es una técnica avanzada para extender la vida útil del objeto temporal ( GotW # 88: Un candidato para el “Const más importante” ), aparentemente no le ayudará en este caso.

Por otro lado, Eric Niebler tiene un artículo que puede interesarte que discute una técnica interesante (aunque enrevesada) que podría permitir a los constructores de tu clase deducir si se le ha pasado un objeto temporal (en realidad un valor r) (y por lo tanto, tiene que ser copiado) o no temporal (lvalue) como se aprobó (y por lo tanto podría potencialmente tener una referencia escondida en lugar de copiar):

  • Amor condicional: FOREACH Redux

Buena suerte con eso, cada vez que leo el artículo, tengo que trabajar en todo como si nunca hubiera visto el material. Solo me queda por un momento fugaz …

Y debo mencionar que las referencias rvalue de C ++ 0x deberían hacer innecesarias las técnicas de Niebler. Las referencias de Rvalue serán compatibles con MSVC 2010, que está progtwigdo para lanzarse en una semana más o menos (el 12 de abril de 2010 si recuerdo correctamente). No sé cuál es el estado de las referencias de rvalue en GCC.

Las referencias temporales const solo tienen el tiempo de vida de la statement actual (es decir, salen del scope justo antes del punto y coma). Por lo tanto, la regla empírica nunca es confiar en una referencia constante existente más allá de la vida útil de la función que la recibe como parámetro, en este caso, eso es solo el constructor. Entonces, una vez que el constructor finaliza, no confíe en ninguna referencia constante para estar cerca.

No hay forma de cambiar / anular / extender esta vida para los temporales. Si desea una vida útil más larga, use un objeto real y no temporal:

 adapter a, b; container(a, b); // lifetime is the lifetime of a and b 

O mejor aún, simplemente no use referencias constantes a miembros de la clase excepto en las circunstancias más extremas cuando los objetos están estrechamente relacionados y definitivamente no son temporales.

La referencia existirá durante toda la vida útil del container , pero el objeto al que se hace referencia existirá solo durante el tiempo de vida de ese objeto. En este caso, ha vinculado su referencia a un objeto temporal con asignación de almacenamiento automático (“asignación de stack”, si así lo desea, aunque esa no es la nomenclatura de C ++). Por lo tanto, no puede esperar que el temporal exista más allá de la statement en la que se escribió (ya que sale del scope inmediatamente después de la llamada al constructor para el container ). La mejor manera de lidiar con esto es usar una copia, en lugar de una referencia. Ya que está usando una referencia constante, de todos modos, tendrá una semántica similar.

Debes redefinir tu clase como:

 plantilla  
 contenedor de clase 
 {
     público:
         contenedor (const T & first, const T & second): primero (primero), segundo (segundo) {}
     privado:
         const T primero;
         const T segundo;
 };

De forma alternativa, podría darle un nombre a sus objetos para evitar que salgan del scope:

    adaptador primero;
    segundo adaptador;
    contenedor c (primero, segundo);

Sin embargo, no creo que sea una buena idea, ya que una statement como return c no es válida.

Editar
Si su objective es compartir objetos para evitar el costo de la copia, entonces debería considerar el uso de objetos de puntero inteligente. Por ejemplo, podemos redefinir su objeto utilizando punteros inteligentes de la siguiente manera:

 plantilla  
 contenedor de clase 
 {
     público:
         contenedor (const boost :: shared_ptr  & first, const boost :: shared_ptr  & second): primero (primero), segundo (segundo) {}
     privado:
         boost :: shared_ptr  primero;
         boost :: shared_ptr  second;
 };

Puede usar:

 boost :: shared_ptr  first (nuevo adaptador);
 boost :: shared_ptr  second (nuevo adaptador);
 contenedor  c (primero, segundo);

O bien, si desea tener copias mutables de primer y segundo localmente:

 boost :: shared_ptr  primero (nuevo adaptador);
 boost :: shared_ptr  second (nuevo adaptador);
 contenedor  c (boost :: const_pointer_cast  (primero), boost :: const_pointer_cast  (segundo));

Si desea evitar la copia, entonces supongo que el contenedor debe crear las instancias almacenadas.

Si desea invocar el constructor predeterminado, entonces no debería ser un problema. Solo invoque el constructor predeterminado de Container.

Probablemente sea más problemático si desea invocar un constructor no predeterminado del tipo contenido. C ++ 0x tendrá mejores soluciones para eso.

Como ejercicio, el contenedor puede aceptar una T o un objeto que contenga los argumentos para el constructor de T. Esto todavía se basa en RVO (optimización del valor de retorno).

 template  class construct_with_1 { T1 _1; public: construct_with_1(const T1& t1): _1(t1) {} template  U construct() const { return U(_1); } }; template  class construct_with_2 { T1 _1; T2 _2; public: construct_with_2(const T1& t1, const T2& t2): _1(t1), _2(t2) {} template  U construct() const { return U(_1, _2); } }; //etc for other arities template  construct_with_1 construct_with(const T1& t1) { return construct_with_1(t1); } template  construct_with_2 construct_with(const T1& t1, const T2& t2) { return construct_with_2(t1, t2); } //etc template  T construct(const T& source) { return source; } template  T construct(const construct_with_1& args) { return args.template construct(); } template  T construct(const construct_with_2& args) { return args.template construct(); } template  class Container { public: T first, second; template  Container(const T1& a = T1(), const T2& b = T2()) : first(construct(a)), second(construct(b)) {} }; #include  class Test { int n; double d; public: Test(int a, double b = 0.0): n(a), d(b) { std::cout < < "Test(" << a << ", " << b << ")\n"; } Test(const Test& x): n(xn), d(xd) { std::cout << "Test(const Test&)\n"; } void foo() const { std::cout << "Test.foo(" << n << ", " << d << ")\n"; } }; int main() { Test test(4, 3.14); Container a(construct_with(1), test); //first constructed internally, second copied a.first.foo(); a.second.foo(); } 

No hagas esto Un temporal se destruye inmediatamente después de la expresión en la que se creó (excepto en el caso de que esté inmediatamente vinculado a una referencia, en cuyo caso es el scope de la referencia). La duración no puede extenderse a la de la clase.

Esta es la razón por la que nunca almaceno miembros como referencias, solo objetos copiados o punteros. Para mí, los indicadores hacen obvio que la vida viene para jugar. Especialmente en el caso de un constructor, no es obvio que sus parámetros de constructor deben sobrevivir a la clase misma.