czas życia wartości zwracanej przez std :: initializer_list

Wdrożenie GCC niszczystd::initializer_list tablica zwrócona z funkcji na końcu pełnego wyrażenia return. Czy to jest poprawne?

Oba przypadki testowe w tym programie pokazują destruktory wykonywane przed użyciem wartości:

#include <initializer_list>
#include <iostream>

struct noisydt {
    ~noisydt() { std::cout << "destroyed\n"; }
};

void receive( std::initializer_list< noisydt > il ) {
    std::cout << "received\n";
}

std::initializer_list< noisydt > send() {
    return { {}, {}, {} };
}

int main() {
    receive( send() );
    std::initializer_list< noisydt > && il = send();
    receive( il );
}

Myślę, że program powinien działać. Ale podstawowe standardy są nieco zawiłe.

Instrukcja return inicjuje obiekt wartości zwracanej tak, jakby został zadeklarowany

std::initializer_list< noisydt > ret = { {},{},{} };

To inicjuje jeden tymczasowyinitializer_list i jego podstawowa pamięć macierzowa z danej serii inicjatorów, a następnie inicjalizuje innąinitializer_list od pierwszego. Jaka jest żywotność tablicy? „Żywotność tablicy jest taka sama jak w przypadkuinitializer_list obiekt. "Ale są dwie z nich, która z nich jest niejednoznaczna. Przykład w 8.5.4 / 6, jeśli działa jak w reklamie, powinien rozwiązać dwuznaczność, że tablica ma czas życia kopiowanego obiektu. Następnie powrót tablica wartości powinna również przetrwać w funkcji wywołującej i powinno być możliwe jej zachowanie poprzez powiązanie jej z nazwanym odnośnikiem.

NaLWS, GCC błędnie zabija tablicę przed powrotem, ale zachowuje nazwęinitializer_list na przykład. Clang przetwarza również przykład poprawnie, ale obiekty na liście sąnigdy zniszczony; spowodowałoby to wyciek pamięci. ICC nie obsługujeinitializer_list w ogóle.

Czy moja analiza jest poprawna?

C ++ 11 §6.6.3 / 2:

Instrukcja zwrotu zlista wzmocnionych-początkowych inicjalizuje obiekt lub odniesienie, które ma zostać zwrócone z funkcji, przez inicjalizację listy kopiowania (8.5.4) z określonej listy inicjalizacyjnej.

8.5.4 / 1:

… Wywoływana jest inicjalizacja listy w kontekście inicjalizacji kopiowaniainicjalizacja listy kopii.

8,5 / 14:

Inicjalizacja występująca w formularzuT x = a; … jest nazywanyinicjalizacja kopiowania.

Powrót do 8.5.4 / 3:

Inicjalizacja listy obiektu lub odniesienia typu T jest zdefiniowana w następujący sposób:…

- W przeciwnym razie, jeśli T jest specjalizacjąstd::initializer_list<E>, aninitializer_list obiekt jest skonstruowany w sposób opisany poniżej i używany do inicjowania obiektu zgodnie z regułami inicjalizacji obiektu z klasy tego samego typu (8.5).

8.5.4 / 5:

Obiekt typustd::initializer_list<E> jest skonstruowany z listy inicjalizującej tak, jakby implementacja przydzieliła tablicęN elementy typuE, gdzieN to liczba elementów na liście inicjalizatora. Każdy element tej tablicy jest inicjowany przez kopiowanie za pomocą odpowiedniego elementu listy inicjalizatora istd::initializer_list<E> obiekt jest skonstruowany tak, aby odnosił się do tej tablicy. Jeśli do zainicjowania któregokolwiek z elementów wymagana jest zwężająca konwersja, program jest źle sformatowany.

8.5.4 / 6:

Żywotność tablicy jest taka sama jak czasinitializer_list obiekt.[Przykład:

typedef std::complex<double> cmplx;
 std::vector<cmplx> v1 = { 1, 2, 3 };
 void f() {
   std::vector<cmplx> v2{ 1, 2, 3 };
   std::initializer_list<int> i3 = { 1, 2, 3 };
 }

Dlav1 iv2, theinitializer_list obiekt i tablica utworzone{ 1, 2, 3 } mieć czas życia pełnego wyrażenia. Dlai3, obiekt i tablica initializer_list mają automatyczny czas życia.- przykład końcowy]

Trochę wyjaśnień na temat zwracania listy wzmocnionej-początkowej

Po zwróceniu pustej listy w nawiasach klamrowych

Instrukcja return z listą początkową wzmocnioną inicjalizuje obiekt lub odniesienie, które ma zostać zwrócone z funkcji, poprzez inicjalizację listy kopiowania (8.5.4) z określonej listy inicjalizatora.

Nie oznacza to, że obiekt zwrócony do zakresu wywoływania jest kopiowany z czegoś. Na przykład jest to ważne:

struct nocopy {
    nocopy( int );
    nocopy( nocopy const & ) = delete;
    nocopy( nocopy && ) = delete;
};

nocopy f() {
    return { 3 };
}

to nie jest:

nocopy f() {
    return nocopy{ 3 };
}

Inicjalizacja kopiowania-listy oznacza po prostu odpowiednik składninocopy X = { 3 } służy do inicjalizacji obiektu reprezentującego wartość zwracaną. To nie wywołuje kopii i jest identyczne z przykładem 8.5.4 / 6 wydłużania czasu życia tablicy.

I Clang i GCCZgodzić się W tym punkcie.

Inne notatki

RecenzjaN2640 nie wspomina o tej narożnej skrzynce. Odnotowano obszerną dyskusję na temat poszczególnych funkcji połączonych tutaj, ale nie widzę niczego o ich interakcji.

Wdrażanie to staje się nieswoiste, ponieważ sprowadza się do zwrócenia opcjonalnej tablicy o zmiennej długości według wartości. Ponieważstd::initializer_list nie jest właścicielem jego zawartości, funkcja musi również zwrócić coś innego, co robi. Podczas przechodzenia do funkcji jest to po prostu lokalna tablica o stałym rozmiarze. Ale w przeciwnym kierunku, VLA musi być zwrócony na stosie, wraz zstd::initializer_listwskazówki. Następnie należy poinformować dzwoniącego, czy ma pozbyć się sekwencji (czy są na stosie, czy nie).

Problem można bardzo łatwo natknąć, zwracając listę początkową z funkcją lambda, jako „naturalny” sposób na zwrócenie kilku tymczasowych obiektów bez dbania o to, jak są one zawarte.

auto && il = []() -> std::initializer_list< noisydt >
               { return { noisydt{}, noisydt{} }; }();

Rzeczywiście, jest to podobne do tego, jak tu przybyłem. Ale pomyłką byłoby pominięcie-> trailing-return-type, ponieważ dedukcja typu zwracana przez lambdę występuje tylko wtedy, gdy zwracane jest wyrażenie, a lista wzmocnionych-początkowych nie jest wyrażeniem.

questionAnswers(2)

yourAnswerToTheQuestion