Durata di riferimento costante C ++ (adattatore contenitore)

Ho un codice che assomiglia a questo:

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

Ho pensato che la vita di riferimento costante sarebbe durata del contenitore. Tuttavia, sembra diversamente, l’object adattatore viene distrutto dopo la creazione del contenitore, lasciando il riferimento ciondolante.

Qual è la vita corretta?

è lo scopo di stack dell’adattatore temporaneo dell’adattatore l’ambito dell’object contenitore o del costruttore contenitore?

come implementare correttamente l’object temporaneo vincolante in riferimento ai membri della class?

Grazie

Secondo lo standard C ++ 03, un riferimento temporaneo a un riferimento ha una durata diversa a seconda del contesto. Nel tuo esempio, penso che si applichi la parte evidenziata in basso (12.2 / 5 “Oggetti temporanei”):

Il temporaneo al quale il riferimento è vincolato o il temporaneo che è l’object completo di un suboggetti di cui il temporaneo è vincolato persiste per la durata del riferimento eccetto come specificato di seguito. Un vincolo temporaneo a un membro di riferimento nel ctor-initializer di un costruttore (12.6.2) persiste finché il costruttore non si chiude. Un vincolo temporaneo a un parametro di riferimento in una chiamata di funzione (5.2.2) persiste fino al completamento dell’espressione completa che contiene la chiamata.

Quindi mentre bind un temporaneo è una tecnica avanzata per estendere la durata dell’object temporaneo ( GotW # 88: Un candidato per il “Most Important const” ), apparentemente non ti aiuterà in questo caso.

D’altra parte, Eric Niebler ha un articolo che potrebbe interessarti che discute una tecnica interessante (se contorta) che potrebbe consentire ai costruttori della tua class di dedurre se un object temporaneo (in realtà un valore rval) è stato passato ad esso (e quindi sarebbe devono essere copiati) o un valore non temporaneo (lvalue) così come è stato passato (e quindi potrebbe potenzialmente avere un riferimento nascosto anziché copiare):

  • Amore condizionato: FOREACH Redux

Buona fortuna anche se ogni volta che leggo l’articolo, devo lavorare su tutto come se non avessi mai visto il materiale prima. Si attacca solo con me per un attimo fugace …

E dovrei ricordare che i riferimenti razionali del C ++ 0x dovrebbero rendere superflue le tecniche di Niebler. I riferimenti di Rvalue saranno supportati da MSVC 2010, che dovrebbe essere rilasciato entro una settimana circa (il 12 aprile 2010 se ricordo correttamente). Non so quale sia lo stato dei riferimenti rvalue in GCC.

I riferimenti const temporanei hanno solo la durata dell’istruzione corrente (cioè, escono dall’ambito appena prima del punto e virgola). Quindi la regola empirica non si basa mai su un riferimento const esistente oltre la durata della funzione che lo riceve come parametro, in questo caso è solo il costruttore. Quindi, una volta che il costruttore è terminato, non fare affidamento su alcun riferimento const per essere ancora in giro.

Non c’è modo di cambiare / sovrascrivere / estendere questa vita per i provvisori. Se vuoi una vita più lunga, usa un object reale e non un temporaneo:

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

O meglio ancora, basta non usare riferimenti costanti ai membri della class, tranne nelle circostanze più terribili in cui gli oggetti sono strettamente correlati e sicuramente non temporanei.

Il riferimento esisterà per l’intera durata del container , ma l’ object a cui si fa riferimento esisterà solo per la durata di quell’object. In questo caso, hai vincolato il tuo riferimento a un object temporaneo con allocazione di memoria automatica (“stack allocation”, se vuoi, anche se questa non è C ++ nomenclatura). Pertanto, non è ansible aspettarsi che il temporaneo esista al di là dell’istruzione in cui è stato scritto (poiché esce immediatamente dopo la chiamata al costruttore per il container ). Il modo migliore per affrontarlo consiste nell’utilizzare una copia, anziché un riferimento. Dal momento che stai usando un riferimento const, comunque, avrà semantica simile.

Dovresti ridefinire la tua class come:

 template  
 contenitore di class 
 {
     pubblico:
         container (const T & first, const T & second): first (first), second (second) {}
     privato:
         prima cosa;
         const T secondo;
 };

In alternativa, puoi assegnare un nome ai tuoi oggetti per impedire che escano dall’ambito:

    adattatore prima;
    adattatore secondo;
    contenitore c (primo, secondo);

Tuttavia, non penso che questa sia una buona idea, dal momento che un’istruzione come return c non è valida.

modificare
Se il tuo objective è condividere oggetti per evitare il costo della copia, dovresti prendere in considerazione l’utilizzo di oggetti puntatori intelligenti. Ad esempio, possiamo ridefinire il tuo object usando puntatori intelligenti come segue:

 template  
 contenitore di class 
 {
     pubblico:
         container (const boost :: shared_ptr  e prima, const boost :: shared_ptr  e second): first (first), second (second) {}
     privato:
         boost :: shared_ptr  prima;
         boost :: shared_ptr  secondo;
 };

Puoi quindi utilizzare:

 boost :: shared_ptr  prima (nuovo adattatore);
 boost :: shared_ptr  secondo (nuovo adattatore);
 contenitore  c (primo, secondo);

Oppure, se si desidera avere copie mutabili di primo e secondo localmente:

 boost :: shared_ptr  prima (nuovo adattatore);
 boost :: shared_ptr  secondo (nuovo adattatore);
 container  c (boost :: const_pointer_cast  (first), boost :: const_pointer_cast  (second));

Se vuoi evitare di copiare, suppongo che il Container debba creare le istanze memorizzate.

Se si desidera richiamare il costruttore predefinito, non dovrebbe esserci alcun problema. Basta richiamare il costruttore predefinito di Container.

Probabilmente è più problematico se si desidera richiamare un costruttore non predefinito del tipo contenuto. C ++ 0x avrà soluzioni migliori per questo.

Come esercizio, il contenitore può accettare un T, o un object contenente gli argomenti per il costruttore di T. Questo si basa ancora su RVO (ottimizzazione del valore di ritorno).

 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(); } 

Non farlo. Un temporaneo viene distrutto immediatamente dopo l’espressione in cui è stato creato (tranne nel caso in cui sia immediatamente associato a un riferimento, nel qual caso è lo scopo del riferimento). La vita non può essere estesa a quella della class.

Questo è il motivo per cui non memorizzo mai i membri come riferimenti: solo oggetti copiati o puntatori. Per me, i puntatori rendono ovvio che la vita entra in gioco. Soprattutto nel caso di un costruttore, non è ovvio che i parametri del tuo costruttore debbano sopravvivere alla class stessa.