Czy destruktor obiektu lokalnego w pętli ma gwarancję, że zostanie wywołany przed następną iteracją?

11

Kiedy mam pętlę i wewnątrz tej pętli tworzę nową zmienną stosu (nieprzydzielając jej na stercie i zmiennej utrzymującej ją zadeklarowaną wewnątrz korpusu pętli), to jest gwarantem, że destruktor tego obiektu zostanie wywołany przed rozpoczęciem kolejnej iteracji, lub może rozwijanie pętli przez kompilator coś zmienić?

użytkownik1282931
źródło
1
Rozwijanie pętli per se nie zmieni kolejności wykonywania. Jednak może to zrobić równoległa pętla .
Adrian Mole,

Odpowiedzi:

8

Od n4800:

§6.3.3 Zakres bloku :

Nazwa zadeklarowana w bloku (8.3) jest lokalna dla tego bloku; ma zakres blokowy. Jego potencjalny zakres zaczyna się w punkcie deklaracji (6.3.2) i kończy na końcu bloku. Zmienna zadeklarowana w zakresie bloku jest zmienną lokalną.

§ 10.3.6 Niszczyciele :

Destruktor jest wywoływany niejawnie [...] po wyjściu z bloku, w którym tworzony jest obiekt (8.7)

§4.1.1 Maszyna abstrakcyjna :

Przepis ten jest czasem nazywany zasadą „jak gdyby”, ponieważ implementacja może zignorować dowolny wymóg tego dokumentu, o ile wynik jest tak, jakby wymóg został spełniony, o ile można to ustalić na podstawie obserwowalnego zachowania program .

[Moje podkreślenie]

Więc tak. Twoja zmienna wykracza poza zakres na końcu pętli (która jest blokiem), a zatem jej destruktor jest wywoływany tak daleko, jak każdy, kto obserwuje zachowanie programu .

Paul Evans
źródło
1
Nie ma w tym nic o KIEDY wywoływany jest destruktor.
surowy
2
@stark To, co pozwala im to zrobić, to zasada as-if. Norma określa tylko zachowanie maszyny abstrakcyjnej. Nie jestem pewien, czy konieczne jest podanie wszystkich tych szczegółów w odpowiedziach tutaj.
Max Langhof,
2
@stark To jest, IMO, nieistotne dla pytania. Równie dobrze możesz powiedzieć, że destruktory mogą być wbudowane, a zatem callw ogóle nie być edytowane . Lub, jeśli skutecznie (jak gdyby) nic nie robią, może nie być żadnego zestawu dla takich generatorów.
Daniel Langr
2
@stark Zobacz Czym dokładnie jest zasada „jak gdyby”? .
Daniel Langr
2
@stark Gdzie zdefiniowano co ? Zauważ, że ta dyskusja jest nie na temat pytania. Możesz zadać kolejne osobne pytanie dotyczące tego problemu.
Daniel Langr,
8

Tak. Łatwiej jest zwizualizować, gdy weźmie się pod uwagę „bloki”, w których deklaruje się zmienną, tj. Między którą parą nawiasów klamrowych. Pętla sama w sobie jest blokiem, a gdy osiągnie nawias zamykający, przed następną iteracją wywoływane są wszystkie destruktory automatycznych zmiennych pamięci zadeklarowanych w pętli.

czy rozwijanie się pętli przez kompilator może coś w tym zmienić?

Zasadniczo nie myśl o tym, co kompilator zoptymalizuje, ponieważ nadal musi gwarantować zachowanie twojego programu, bez względu na to, co robi, aby go zoptymalizować. W takim przypadku rozwinięcie pętli nie zmieni niczego w tym zakresie, jeśli tak się stanie.

JBL
źródło
2
+1 za ogólną zasadę, pisząc kod nie należy się martwić o wewnętrzne kompilatory.
Chciałem
copy-elision i RVO zmieniają zachowanie programu, prawda?
Jean-Baptiste Yunès,
@ Jean-BaptisteYunès Mogą potencjalnie jednak standard zezwala im również na[class.copy.elision]
ChrisMM
Nie tylko para aparatów ortodontycznych . Możesz pisać, for(...) X x{};a xobiekt będzie konstruowany + niszczony w każdej iteracji. Demo na żywo . Odpowiednią sekcją standardową jest stmt.iter / 2 .
Daniel Langr,
@DanielsaysreinstateMonica Zgodnie z §9.5.2 [stmt.iter]jest to czysto równoważne ( wyróżnienie moje): „Jeśli podstacja w instrukcji iteracji jest pojedynczą instrukcją, a nie instrukcją złożoną , to tak, jakby została przepisana na instrukcję złożoną zawierającą oryginalne oświadczenie. ”. Zasadniczo, z nawiasami klamrowymi lub bez dla jednej instrukcji oznacza dokładnie to samo, a nawiasy klamrowe są niejawne. Pominąłem to dla jasności.
JBL,
2

Destruktor jest wywoływany przy każdej iteracji. Dlatego w niektórych przypadkach szybsze jest zadeklarowanie zmiennej poza pętlą zamiast w pętli. Zakładając następujący przypadek:

std::string temp;
for(int i = 0; i < 10; ++i){
    temp = arr[i];
    doSomething(temp);
}

Destruktor nie jest wywoływany podczas wykonywania pętli. Po prostu zastępuje temp.

Ale jeśli użyjesz std::string temp = arr[i]konstruktora i wywoływany jest destruktor dla każdej iteracji. Myślę, że to dodaje trochę czasu działania w przypadku, gdy pętla jest wykonywana bardzo często.

Julian Schnabel
źródło
zwróć uwagę, że wywoływanie destruktorów lub nie jest tylko kwestią wydajności. Jeśli masz typ RAII, w szczególności chcesz, aby destruktor był wywoływany przy każdej iteracji
idclev 463035818
nie jestem pewien, czy to prawda. Czy destruktor zawartości „temp”, który utrzymuje z poprzedniej iteracji, nie jest wywoływany, gdy temp zostaje ponownie przypisany do nowej wartości?
user1282931,
Nie jestem też w 100% pewien. Popraw moją odpowiedź, jeśli znalazłeś coś złego. :)
Julian Schnabel,
0

Destruktor jest wywoływany przed następną iteracją

Girspoon
źródło
0

Oczywiście dtor jest wywoływany na końcu iteracji i rozwijanie pętli nie powinno modyfikować tego zachowania, jak każda inna optymalizacja (optymalizacja nie powinna modyfikować zachowania programu), z wyjątkiem pewnego rodzaju RVO i podobnych, które mogą wyeliminować niektóre semantycznie fałszywe tworzenie obiektów .

Jean-Baptiste Yunès
źródło