2010-04-08 20 views
14

tengo código que se parece a esto:C++ constante de por vida referencia (adaptador del recipiente)

class T {}; 

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

class adapter : T {}; 

container(adapter(), adapter()); 

pensé vida de constante referencia sería tiempo de vida de 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 la vida útil correcta?

es el alcance de la pila del objeto temporal del adaptador el alcance del objeto contenedor o del constructor del contenedor?

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

, gracias

Respuesta

16

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 la porción resaltada a continuación se aplica (12,2/5 "objetos temporales"):

El temporal a la que se une la referencia o el temporal que es el objeto completa a un subobjeto de los cuales 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 temporal vinculado 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.

Así, mientras que la unión temporal es una técnica avanzada para extender el tiempo de vida del objeto temporal (GotW #88: A Candidate For the "Most Important const"), que al parecer no le ayudará en este caso. Por otro lado, Eric Niebler tiene un artículo que puede interesarle que discute una técnica interesante (aunque enrevesada) que podría permitir que los constructores de su clase deduzcan si se le ha pasado un objeto temporal (en realidad, un valor r). (y por lo tanto tendría que ser copiado) o un no temporal (valor-I) tal como se aprobó (y por lo tanto podría potencialmente segura haber una referencia escondido lejos en lugar de copiar):

Buena suerte, sin embargo, 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 de valores de C++ 0x deberían hacer innecesarias las técnicas de Niebler. Las referencias de Rvalue serán compatibles con MSVC 2010, que está programado 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.

+0

Creo que, en este caso, el temporal está vinculado a un parámetro de llamada de función (la llamada al constructor) como en la siguiente oración. Sí, también está vinculado al miembro debido al aliasing en el inicializador de ctor, y sí, persistirá hasta que el constructor salga (más tiempo, de hecho, si la expresión completa que contiene la llamada del constructor también hace otras cosas). Pero creo que el pasaje resaltado se refiere a cosas como 'struct container {const & adapter a; contenedor(): a (adaptador()) {}}; '. –

+0

@Steve: en una mirada más cercana, creo que tienes razón, actualizaré la respuesta (aunque el mismo resultado). –

6

referencias const temporales sólo tienen el tiempo de vida del estado de cuenta actual (es decir, que salen de alcance 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 útil para los temporales. Si quieres una vida útil más larga, usar un objeto real y no una temporal:

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

O mejor aún, simplemente no lo utiliza constantes referencias a miembros de la clase, excepto en las circunstancias más graves cuando los objetos están muy estrechamente relacionados y definitivamente no es temporal.

+2

Para ser más precisos, viven hasta el final de la expresión completa que fueron creados en – GManNickG

+1

"No hay manera de cambiar/anulación/extender esta vida para los temporales" -. En realidad no hay, no se trata sólo útil en casos como este. Si usa un temporal para inicializar una referencia constante con la duración automática, la duración del temporal se extenderá hasta que se salga del alcance de la automática. –

1

La referencia existirá durante toda la vida útil de 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 pila", 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 declaración en la que se escribió (ya que sale del alcance inmediatamente después de la llamada al constructor para 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.

Deberá volver a definir su clase como:

 
template<typename T> 
class container 
{ 
    public: 
     container(const T& first, const T& second) : first(first), second(second) {} 
    private: 
     const T first; 
     const T second; 
}; 

Como alternativa, puede dar a sus objetos un nombre para evitar que se salgan de alcance:

 
    adaptor first; 
    adaptor second; 
    container c(first,second); 

Sin embargo, no creo esta es una buena idea, ya que una declaración como return c no es válida.

Editar
Si su objetivo es compartir objetos con el fin de evitar el costo de la copia, entonces usted debe considerar el uso de objetos puntero inteligente.Por ejemplo, podemos redefinir su objeto utilizando punteros inteligentes de la siguiente manera:

 
template<typename T> 
class container 
{ 
    public: 
     container(const boost::shared_ptr<const T>& first, const boost::shared_ptr<const T>& second) : first(first), second(second) {} 
    private: 
     boost::shared_ptr<const T> first; 
     boost::shared_ptr<const T> second; 
}; 

continuación, puede utilizar:

 
boost::shared_ptr<const adaptor> first(new adaptor); 
boost::shared_ptr<const adaptor> second(new adaptor); 
container<adaptor> c(first,second); 

O, si usted quiere tener copias mutables de primer y segundo nivel local:

 
boost::shared_ptr<adaptor> first(new adaptor); 
boost::shared_ptr<adaptor> second(new adaptor); 
container<adaptor> c(boost::const_pointer_cast<const adaptor>(first),boost::const_pointer_cast<const adaptor>(second)); 
+0

los objetos reales son bastante pesados ​​con constructores de efectos secundarios. Estoy tratando de evitar la construcción de copias. – Anycorn

+2

@aaa, en ese caso, debe usar punteros inteligentes como boost :: shared_ptr. –

+0

Pensé en hacer eso, sin embargo, la clase está en la interfaz pública que estoy tratando de mantener libre de impulsos – Anycorn

-1

No haga 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 alcance de la referencia). La duración no puede extenderse a la de la clase.

Es por eso 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.

+1

-1: los punteros deben reemplazarse con referencias siempre que sea posible. –

+0

No lo hice, pero viven hasta el final de la expresión completa en la que se crearon, no el alcance. – GManNickG

+0

Antes que nada, es una declaración ridícula. En segundo lugar, en este caso las referencias hacen que este comportamiento no sea obvio. Lame -1. – Stephen

0

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 un ejercicio, el contenedor puede aceptar una T, o un objeto que contiene los argumentos para el constructor de T. Esto todavía se basa en RVO (optimización del valor de retorno).

template <class T1> 
class construct_with_1 
{ 
    T1 _1; 
public: 
    construct_with_1(const T1& t1): _1(t1) {} 
    template <class U> 
    U construct() const { return U(_1); } 
}; 

template <class T1, class T2> 
class construct_with_2 
{ 
    T1 _1; 
    T2 _2; 
public: 
    construct_with_2(const T1& t1, const T2& t2): _1(t1), _2(t2) {} 
    template <class U> 
    U construct() const { return U(_1, _2); } 
}; 

//etc for other arities 

template <class T1> 
construct_with_1<T1> construct_with(const T1& t1) 
{ 
    return construct_with_1<T1>(t1); 
} 

template <class T1, class T2> 
construct_with_2<T1, T2> construct_with(const T1& t1, const T2& t2) 
{ 
    return construct_with_2<T1, T2>(t1, t2); 
} 

//etc 
template <class T> 
T construct(const T& source) { return source; } 

template <class T, class T1> 
T construct(const construct_with_1<T1>& args) 
{ 
    return args.template construct<T>(); 
} 

template <class T, class T1, class T2> 
T construct(const construct_with_2<T1, T2>& args) 
{ 
    return args.template construct<T>(); 
} 

template <class T> 
class Container 
{ 
public: 
    T first, second; 

    template <class T1, class T2> 
    Container(const T1& a = T1(), const T2& b = T2()) : 
     first(construct<T>(a)), second(construct<T>(b)) {} 
}; 

#include <iostream> 

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(x.n), d(x.d) { std::cout << "Test(const Test&)\n"; } 
    void foo() const { std::cout << "Test.foo(" << n << ", " << d << ")\n"; } 
}; 

int main() 
{ 
    Test test(4, 3.14); 
    Container<Test> a(construct_with(1), test); //first constructed internally, second copied 
    a.first.foo(); 
    a.second.foo(); 
} 
Cuestiones relacionadas