Uwaga: ta odpowiedź dotyczy tylko języka c ++ 11 i nowszych. Nie ma czegoś takiego jak „C / C ++”, są to różne języki.
Nie, nie ma niebezpieczeństwa zwracania obiektu lokalnego według wartości i jest to zalecane. Uważam jednak, że we wszystkich odpowiedziach brakuje ważnej kwestii. Wielu innych twierdziło, że konstrukcja jest kopiowana lub umieszczana bezpośrednio za pomocą RVO. Jednak nie jest to całkowicie poprawne. Spróbuję dokładnie wyjaśnić, jakie rzeczy mogą się zdarzyć podczas zwracania lokalnego obiektu.
Przenieś semantykę
Od czasu C ++ 11 mieliśmy odniesienia do wartości r, które są odniesieniami do obiektów tymczasowych, z których można bezpiecznie ukraść. Na przykład std :: vector ma konstruktor przenoszenia, a także operator przypisania przenoszenia. Oba mają stałą złożoność i po prostu kopiują wskaźnik do danych przenoszonego wektora. Nie będę tutaj wchodził w szczegóły dotyczące semantyki przenoszenia.
Ponieważ obiekt utworzony lokalnie w funkcji jest tymczasowy i wychodzi poza zakres, gdy funkcja zwraca, zwracany obiekt nigdy nie jest kopiowany w c ++ 11 dalej. Konstruktor przenoszenia jest wywoływany na zwracanym obiekcie (lub nie, wyjaśniono później). Oznacza to, że gdybyś miał zwrócić obiekt za pomocą drogiego konstruktora kopiującego, ale niedrogiego konstruktora przenoszenia, takiego jak duży wektor, tylko własność danych jest przenoszona z obiektu lokalnego do obiektu zwracanego - co jest tanie.
Zwróć uwagę, że w twoim konkretnym przykładzie nie ma różnicy między kopiowaniem a przenoszeniem obiektu. Domyślne konstruktory przenoszenia i kopiowania Twojej struktury powodują te same operacje; kopiowanie dwóch liczb całkowitych. Jest to jednak co najmniej tak szybkie, jak jakiekolwiek inne rozwiązanie, ponieważ cała struktura mieści się w 64-bitowym rejestrze procesora (popraw mnie, jeśli się mylę, nie znam zbyt wielu rejestrów procesora).
RVO i NRVO
RVO oznacza optymalizację wartości zwrotu i jest jedną z nielicznych optymalizacji wykonywanych przez kompilatory, która może mieć skutki uboczne. Od C ++ 17 wymagane jest RVO. Zwracając nienazwany obiekt, jest on konstruowany bezpośrednio w miejscu, w którym obiekt wywołujący przypisuje zwracaną wartość. Nie jest wywoływany ani konstruktor kopiujący, ani konstruktor przenoszenia. Bez RVO nienazwany obiekt zostałby najpierw skonstruowany lokalnie, następnie przeniesiony skonstruowany w zwróconym adresie, a następnie lokalny nienazwany obiekt zostałby zniszczony.
Przykład, gdzie RVO jest wymagane (c ++ 17) lub prawdopodobne (przed c ++ 17):
auto function(int a, int b) -> MyStruct {
return MyStruct{a, b};
}
NRVO oznacza optymalizację nazwanej wartości zwrotnej i jest tym samym, co RVO, z wyjątkiem tego, że jest wykonywane dla nazwanego obiektu lokalnie dla wywoływanej funkcji. Wciąż nie jest to gwarantowane przez standard (c ++ 20), ale wiele kompilatorów nadal to robi. Zauważ, że nawet z nazwanymi obiektami lokalnymi, w najgorszym przypadku są one przenoszone po zwróceniu.
Wniosek
Jedynym przypadkiem, w którym powinieneś rozważyć nie zwracanie przez wartość, jest nazwany, bardzo duży (jak w rozmiarze stosu) obiekt. Dzieje się tak, ponieważ NRVO nie jest jeszcze gwarantowane (od c ++ 20), a nawet przesuwanie obiektu byłoby powolne. Moim zaleceniem i zaleceniem w Cpp Core Guidelines jest, aby zawsze preferować zwracanie obiektów według wartości (jeśli zwraca wiele wartości, użyj struktury struct (lub krotki)), gdzie jedynym wyjątkiem jest sytuacja, w której obiekt jest drogi do przeniesienia. W takim przypadku użyj parametru odniesienia innego niż const.
NIGDY nie jest dobrym pomysłem zwracanie zasobu, który musi zostać ręcznie zwolniony z funkcji w języku c ++. Nigdy tego nie rób. Przynajmniej użyj std :: unique_ptr lub stwórz własną nielokalną lub lokalną strukturę z destruktorem, który zwalnia swój zasób ( RAII ) i zwraca jego instancję. W takim przypadku dobrym pomysłem byłoby zdefiniowanie konstruktora przenoszenia i operatora przypisania przenoszenia, jeśli zasób nie ma własnej semantyki przenoszenia (i usuwa konstruktor kopiujący / przypisanie).