2016-02-10 30 views
7

Podaj przykładowy kod, jakie zasady dotyczą okresu ważności tymczasowego ciągu znaków przekazywanego do S.Element referencyjny agregatu i jego tymczasowe okresy ważności

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 

według normy:

N4140 12,2 P5.1 (usunięto N4296)

tymczasowa związana z członem odniesienia w konstruktora konstruktor-inicjator (12.6.2) utrzymuje się do czasu zakończenia konstruktora .

N4296 12.6.2 P8

tymczasowa ekspresja związany z elementem odniesienia w MEM-inicjator jest słabo formowane.

Zatem posiadanie zdefiniowanego przez użytkownika konstruktora, takiego jak [1], zdecydowanie nie jest tym, czego chcemy. Ma być nawet źle sformułowany w najnowszym C++ 14 (czy też?) Ani gcc, ani clang nie ostrzegają o tym.
Czy zmienia się z bezpośrednim inicjowaniem agregacji? Wygląda na to, że tymczasowe życie jest przedłużone.

W odniesieniu do inicjowania kopiowania, Default move constructor and reference members stwierdza, że ​​[2] jest niejawnie wygenerowany. Biorąc pod uwagę fakt, że ruch może zostać usunięty, czy ta sama reguła ma zastosowanie do domyślnie wygenerowanego konstruktora ruchu?

Który z a, b, c, d ma prawidłowy numer referencyjny?

+0

Nie ma wyjątków od przedłużenia żywotności modułów tymczasowych dla inicjowania agregacji, dlatego okres ważności tymczasowego zostanie wydłużony. Gwarantuje to odpowiedni czas życia tymczasowego utworzonego w przypadku "bezpośredniego inicjowania". – dyp

+0

co masz na myśli mówiąc "ruch może zostać usunięty"? Wiązanie referencji nie może zostać usunięte, 'str_' wiąże się bezpośrednio z' other.str'. ('std :: move' nie ma żadnego efektu) –

+0

@ M.M Mam na myśli, że większość kompilatorów będzie wykonywać bezpośrednią inicjalizację, zamiast używać konstruktora ruchu. Tak 'std :: move (inne) .str' jest takie samo jak' other.str' dla referencji i nie ma tutaj żadnego efektu – 3XX0

Odpowiedz

2

Żywotność tymczasowych obiektów powiązanych z odniesieniami zostaje przedłużona, chyba że istnieje wyjątek. Oznacza to, że jeśli nie ma takiego wyjątku, wówczas żywotność zostanie przedłużona.

Od dość niedawnym projekcie, N4567:

Drugi kontekst [gdzie życie jest rozszerzony] jest, gdy odniesienia związany z tymczasowym. Tymczasowego, do którego odniesienie jest związany lub tymczasowe pełna przedmiotem podobiektu do których odniesienia związanego utrzymuje się przez cały czas trwania odniesienie wyjątkiem:

  • (5.1) tymczasowego obiektu związanego do parametru odniesienia w wywołaniu funkcji (5.2.2) trwa do zakończenia pełnego wyrażenia zawierającego połączenie.
  • (5.2) Czas życia tymczasowego powiązania z wartością zwróconą w instrukcji return funkcji (6.6.3) nie jest przedłużany; tymczasowy jest zniszczony na końcu pełnego wyrażenia w instrukcji return.
  • (5.3) Czasowe powiązanie z odwołaniem w nowym inicjatorze (5.3.4) trwa do momentu zakończenia pełnego wyrażenia zawierającego nowy inicjator.

Jedyna istotna zmiana C++ 11 jest, jak wspomniano PO, że C++ 11 znajduje się dodatkowa wyjątkiem członków rodzajów danych referencyjnych (z N3337):

  • Ograniczenie tymczasowe do elementu referencyjnego w inicjatorze ctor konstruktora (12.6.2) trwa do momentu zakończenia konstruktora.

została usunięta, w CWG 1696 (po-C++ 14) i tymczasowe wiązanie obiektów odniesienia elementów danych przez MEM-inicjator jest słabo formowane.


Odnośnie przykładów w PO:

struct S 
{ 
    const std::string& str_; 
}; 

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

ta tworzy tymczasowy std::string i inicjuje człon str_ danych z niego. S a{"foo"} używa inicjowania agregacji, więc nie ma w tym celu inicjatora pamięci. Nie ma zastosowania żaden wyjątek dotyczący przedłużenia okresu ważności, dlatego okres ważności tego tymczasowego okresu jest przedłużany do okresu użytkowania elementu danych odniesienia str_.


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

Przed obowiązkową kopiowania elizji z C++ 17: Formalnie możemy utworzyć tymczasowy std::string zainicjować tymczasowej S poprzez wiązanie tymczasowy std::string członkowi str_ referencyjnej . Następnie przenosimy ten tymczasowy kod do S w b. Spowoduje to "skopiowanie" odniesienia, które nie wydłuży czasu życia tymczasowego. Jednak implementacje spowodują przeniesienie z tymczasowego adresu URL do S do b. Nie może to jednak wpływać na czas życia tymczasowego. Można zaobserwować to w następującym programie:

#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

Zauważ, że destruktor z loud nazywa się przed „końcem główne”, natomiast x życie dopiero po tym śladu. Formalnie tymczasowy loud jest niszczony na końcu pełnego wyrażenia, które go utworzyło.

Zachowanie się nie zmienia, jeśli konstruktor ruchu aggr jest zdefiniowany przez użytkownika.

Z obowiązkowego kopiowaniem elizji w C++ 17: Identyfikujemy obiekt na RHS S{"bar"} z obiektu na LHS b. Powoduje to wydłużenie czasu trwania tymczasowego do czasu życia b. Zobacz CWG 1697.


Dla pozostałych dwóch przykładów konstruktor ruchu - jeśli jest wywołany - po prostu kopiuje odniesienie. Konstruktor ruchu (z S) może być oczywiście usunięty, ale nie można tego zaobserwować, ponieważ kopiuje tylko odniesienie.

+0

Być może 'auto b = S {" bar "};' zmieni się na C++ 17? Intencją zmian wartości pryncypiów jest to, że 'S s {x};' powinno być dokładnie takie samo jak 'auto s = S {x};', chociaż nie sprawdziłem dokładnego sformułowania, które obejmowałoby ten przypadek. –

+0

@MM Nie jest mi łatwo zrozumieć nowe brzmienie (* "wyrażenie inicjalizacyjne służy do inicjowania obiektu docelowego" *, 17.6.1), ale https://wg21.link/cwg1697 sugeruje, że 'b' i 'S {" bar "}" odnoszą się do tego samego obiektu, dlatego czas życia tymczasowego powinien zostać przedłużony do okresu życia "b". Nie sądzę jednak, że kompilatory obecnie to implementują (patrz: Live demo odpowiedzi). – dyp