Rozumiem, że w C ++ 11, gdy zwracasz zmienną lokalną z funkcji według wartości, kompilator może traktować tę zmienną jako odwołanie do wartości r i „przenosić” ją z funkcji, aby ją zwrócić (jeśli Oczywiście RVO / NRVO się nie zdarza).
Moje pytanie brzmi: czy to nie może złamać istniejącego kodu?
Rozważ następujący kod:
#include <iostream>
#include <string>
struct bar
{
bar(const std::string& str) : _str(str) {}
bar(const bar&) = delete;
bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
void print() {std::cout << _str << std::endl;}
std::string _str;
};
struct foo
{
foo(bar& b) : _b(b) {}
~foo() {_b.print();}
bar& _b;
};
bar foobar()
{
bar b("Hello, World!");
foo f(b);
return std::move(b);
}
int main()
{
foobar();
return EXIT_SUCCESS;
}
Myślałem, że możliwe byłoby, aby destruktor obiektu lokalnego odwoływał się do obiektu, który zostaje niejawnie poruszony, a zatem nieoczekiwanie zobaczyłby „pusty” obiekt. Próbowałem to sprawdzić (patrz http://ideone.com/ZURoeT ), ale mam „prawidłowy” wynik bez wyraźnej std::move
in foobar()
. Zgaduję, że było to spowodowane NRVO, ale nie próbowałem zmienić kodu, aby to wyłączyć.
Czy mam rację, że ta transformacja (powodująca wyjście z funkcji) zachodzi niejawnie i może uszkodzić istniejący kod?
AKTUALIZACJA Oto przykład, który ilustruje to, o czym mówię. Poniższe dwa łącza dotyczą tego samego kodu. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11
Jeśli spojrzysz na wynik, jest inny.
Sądzę więc, że to pytanie się teraz stało, czy zostało to wzięte pod uwagę przy dodawaniu niejawnego przeniesienia do standardu, i zdecydowano, że dodanie tej przełomowej zmiany jest w porządku, ponieważ ten rodzaj kodu jest dość rzadki? Zastanawiam się także, czy jakieś kompilatory będą ostrzegać w takich przypadkach ...
Odpowiedzi:
Scott Meyers opublikował na comp.lang.c ++ (sierpień 2010 r.) O problemie polegającym na tym, że niejawne generowanie konstruktorów ruchu mogło złamać niezmienniki klasy C ++ 03:
Problem polega na tym, że w C ++ 03
X
miał niezmiennik, że jegov
członek zawsze miał 5 elementów.X::~X()
liczył na ten niezmiennik, ale nowo wprowadzony konstruktor ruchu przeniósł sięv
, ustawiając w ten sposób jego długość na zero.Jest to związane z twoim przykładem, ponieważ zepsuty niezmiennik jest wykrywany tylko w
X
destruktorze (jak mówisz, możliwe jest, że destruktor obiektu lokalnego odwołuje się do obiektu, który zostaje niejawnie przeniesiony, a zatem nieoczekiwanie widzi pusty obiekt).C ++ 11 stara się osiągnąć równowagę między zerwaniem części istniejącego kodu a dostarczeniem użytecznych optymalizacji opartych na konstruktorach move.
Komitet początkowo zdecydował, że konstruktory ruchów i operatory przydziału ruchów powinny być generowane przez kompilator, o ile użytkownik tego nie zapewni.
Następnie zdecydował, że to rzeczywiście był powód do alarmu i ograniczył automatyczne generowanie konstruktorów ruchu i operatorów przypisania ruchu w taki sposób, że jest znacznie mniejsze, choć nie niemożliwe, uszkodzenie istniejącego kodu (np. Jawnie zdefiniowany destruktor).
Kuszące jest myślenie, że zapobieganie generowaniu niejawnych konstruktorów ruchu, gdy obecny jest destruktor zdefiniowany przez użytkownika, jest wystarczające, ale nie jest to prawdą ( N3153 - Implicit Move Must Go for more details).
W N3174 - Aby przenieść lub nie, aby przenieść Stroupstrup mówi:
źródło