La durata degli oggetti temporanei associati ai riferimenti viene estesa, a meno che non vi sia un'eccezione specifica. Cioè, se non c'è una tale eccezione, allora la durata sarà estesa.
da un abbastanza recente progetto, N4567:
Il secondo contesto [in cui la durata è estesa] è quando un riferimento è associata a una temporanea. La temporanea in cui il riferimento è associato o il temporaneo che è l'oggetto completo di un subobject a cui è vincolato il riferimento persiste per la durata riferimento eccetto:
- (5,1) Un oggetto temporaneo associato a un parametro di riferimento in una chiamata di funzione (5.2.2) persiste fino al completamento dell'espressione completa che contiene la chiamata.
- (5.2) La durata di un vincolo temporaneo al valore restituito in una dichiarazione di ritorno funzione (6.6.3) non è estesa; il temporaneo è distrutto alla fine dell'espressione completa nell'istruzione return.
- (5.3) Un riferimento temporaneo a un riferimento in un nuovo inizializzatore (5.3.4) persiste fino al completamento dell'espressione completa contenente il nuovo inizializzatore.
L'unico cambiamento significativo C++ 11 è, come detto OP, che in C++ 11 ci fu un'eccezione supplementare per i membri dati di tipi di riferimento (da N3337):
- Un vincolo temporaneo a un membro di riferimento nell'inizializzatore di un costruttore (12.6.2) del costruttore persiste finché il costruttore non si chiude.
Questo è stato rimosso in CWG 1696 (post-C++ 14), e vincolante oggetti temporanei per fare riferimento a componenti di dati tramite la mem-inizializzatore è ora mal formata.
Per quanto riguarda gli esempi nel PO:
struct S
{
const std::string& str_;
};
S a{"foo"}; // direct-initialization
Questo crea una temporanea std::string
e inizializza il membro di dati str_
con esso. Il S a{"foo"}
utilizza l'inizializzazione di aggregazione, quindi non è coinvolto alcun inizializzatore di mem. Nessuna delle eccezioni per le estensioni di durata si applica, pertanto la durata di tale temporaneo viene estesa alla durata del membro di dati di riferimento str_
.
auto b = S{"bar"}; // copy-initialization with rvalue
Prima elisione copia obbligatoria con C++ 17: Formalmente, si crea una temporanea std::string
, inizializzare una temporanea S
legandosi temporanea std::string
all'elemento str_
riferimento . Quindi, spostiamo quello S
temporaneo in b
. Ciò "copierà" il riferimento, che non prolungherà la durata del periodo di prova di std::string
. Tuttavia, le implementazioni porteranno il passaggio dal numero provvisorio S
a b
. Ciò non deve tuttavia influire sulla durata del temporaneo std::string
. È possibile osservare questo nel seguente programma:
#include <iostream>
#define PRINT_FUNC() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
struct loud
{
loud() PRINT_FUNC()
loud(loud const&) PRINT_FUNC()
loud(loud&&) PRINT_FUNC()
~loud() PRINT_FUNC()
};
struct aggr
{
loud const& l;
~aggr() PRINT_FUNC()
};
int main() {
auto x = aggr{loud{}};
std::cout << "end of main\n";
(void)x;
}
Live demo
Si noti che il distruttore di loud
viene chiamato prima la "fine della principale", mentre x
vita fino a dopo quella traccia. Formalmente, il temporaneo loud
viene distrutto alla fine dell'espressione completa che lo ha creato.
Il comportamento non cambia se il costruttore di spostamento di aggr
è definito dall'utente.
Con obbligatoria copia-elisione C++ 17: identifichiamo l'oggetto sulle sd S{"bar"}
con l'oggetto sul lato sinistro b
. Ciò causa l'estensione della durata del temporaneo alla durata di b
. Vedi CWG 1697.
Per i restanti due esempi, il costruttore di movimento, se chiamato, copia semplicemente il riferimento Il costruttore di movimento (di S
) può essere eliminato, ovviamente, ma ciò non è osservabile poiché copia solo il riferimento.
Non vi è alcuna eccezione dalle estensioni del tempo dei temporaries per l'inizializzazione degli aggregati, quindi la durata del temporaneo verrà estesa. Ciò garantisce una durata adeguata per il temporaneo creato nel caso di "inizializzazione diretta". – dyp
cosa intendi con "la mossa potrebbe essere eliminata"? L'associazione di riferimento non può essere elidata, 'str_' si lega direttamente a' other.str'. (lo 'std :: move' non ha effetto) –
@ M.M Voglio dire che la maggior parte dei compilatori eseguirà un'inizializzazione diretta piuttosto che usare il costruttore di movimento. Sì 'std :: move (other) .str' è lo stesso di' other.str' per i riferimenti e non ha effetto qui – 3XX0