2016-02-10 42 views
7

Dato questo esempio di codice, quali sono le regole relative alla durata della stringa temporanea passata a S.Membro di riferimento aggregato e durata temporanea

struct S 
{ 
    // [1] S(const std::string& str) : str_{str} {} 
    // [2] S(S&& other) : str_{std::move(other).str} {} 

    const std::string& str_; 
}; 

S a{"foo"}; // direct-initialization 

auto b = S{"bar"}; // copy-initialization with rvalue 

std::string foobar{"foobar"}; 
auto c = S{foobar}; // copy-initialization with lvalue 

const std::string& baz = "baz"; 
auto d = S{baz}; // copy-initialization with lvalue-ref to temporary 

Secondo lo standard:

N4140 12,2 P5.1 (rimosso in N4296)

Un temporaneo legato ad un organo di riferimento in ctor-inizializzatore di un costruttore (12.6.2) persiste fino all'uscita dal costruttore .

N4296 12.6.2 p8

Un'espressione temporanea legato ad un organo di riferimento in una mem-inizializzatore è mal formate.

Quindi avere un costruttore definito dall'utente come [1] non è definitivamente quello che vogliamo. Si suppone che sia malformato nell'ultimo C++ 14 (o è vero?) Né gcc né clang ne hanno avvertito.
Cambia con l'inizializzazione di aggregazione diretta? In questo caso, mi sembra che la durata temporanea sia stata estesa.

Ora per quanto riguarda l'inizializzazione della copia, Default move constructor and reference members afferma che viene generato in modo implicito. Dato che la mossa potrebbe essere eliminata, la stessa regola si applica al costruttore di mosse generato implicitamente?

Quale di a, b, c, d ha un riferimento valido?

+0

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

+0

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) –

+0

@ 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

risposta

2

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.

+0

Forse 'auto b = S {" bar "};' cambierà per C++ 17? L'intento delle modifiche ai prvalues ​​è che 's s {x};' dovrebbe essere esattamente identico a 'auto s = S {x};', anche se non ho controllato il testo esatto che avrebbe coperto questo caso. –

+0

@MM Non è molto facile per me comprendere la nuova dicitura (* "l'espressione di inizializzazione viene utilizzata per inizializzare l'oggetto di destinazione" *, 17.6.1) ma https://wg21.link/cwg1697 suggerisce che 'b' e 'S {" bar "}' si riferisce allo stesso oggetto, quindi la durata del temporaneo dovrebbe essere estesa alla durata di 'b'. Non penso che i compilatori attualmente lo implementino, tuttavia (vedi Demo di risposta dal vivo). – dyp

Problemi correlati