Wiem, że tytuł brzmi znajomo, ponieważ jest wiele podobnych pytań, ale proszę o inny aspekt problemu (znam różnicę między posiadaniem rzeczy na stosie a układaniem ich na stosie).
W Javie zawsze mogę zwrócić odniesienia do obiektów „lokalnych”
public Thing calculateThing() {
Thing thing = new Thing();
// do calculations and modify thing
return thing;
}
W C ++, aby zrobić coś podobnego, mam 2 opcje
(1) Mogę używać referencji, gdy potrzebuję „zwrócić” obiekt
void calculateThing(Thing& thing) {
// do calculations and modify thing
}
Następnie użyj tego w ten sposób
Thing thing;
calculateThing(thing);
(2) Lub mogę zwrócić wskaźnik do dynamicznie przydzielonego obiektu
Thing* calculateThing() {
Thing* thing(new Thing());
// do calculations and modify thing
return thing;
}
Następnie użyj tego w ten sposób
Thing* thing = calculateThing();
delete thing;
Korzystając z pierwszego podejścia, nie będę musiał ręcznie zwalniać pamięci, ale dla mnie utrudnia to odczytanie kodu. Problem z drugim podejściem polega na tym, że będę musiał pamiętać delete thing;
, co nie wygląda całkiem ładnie. Nie chcę zwracać skopiowanej wartości, ponieważ jest nieefektywna (tak mi się wydaje), więc oto pytania
- Czy istnieje trzecie rozwiązanie (które nie wymaga kopiowania wartości)?
- Czy jest jakiś problem, jeśli będę trzymać się pierwszego rozwiązania?
- Kiedy i dlaczego powinienem skorzystać z drugiego rozwiązania?
źródło
Odpowiedzi:
Udowodnij to.
Wyszukaj RVO i NRVO oraz w semantyce ruchu C ++ 0x. W większości przypadków w C ++ 03 parametr out jest po prostu dobrym sposobem na uczynienie kodu brzydkim, aw C ++ 0x faktycznie krzywdziłbyś siebie używając parametru out.
Po prostu napisz czysty kod i zwróć według wartości. Jeśli problemem jest wydajność, sprofiluj ją (przestań zgadywać) i dowiedz się, co możesz zrobić, aby to naprawić. Prawdopodobnie nie zwróci rzeczy z funkcji.
To powiedziawszy, jeśli nie masz ochoty pisać w ten sposób, prawdopodobnie chciałbyś zrobić parametr out. Unika dynamicznej alokacji pamięci, co jest bezpieczniejsze i generalnie szybsze. Wymaga to sposobu na skonstruowanie obiektu przed wywołaniem funkcji, co nie zawsze ma sens dla wszystkich obiektów.
Jeśli chcesz korzystać z alokacji dynamicznej, możesz przynajmniej umieścić ją w inteligentnym wskaźniku. (I tak powinno być to robione przez cały czas) Wtedy nie martwisz się o usunięcie czegokolwiek, wszystko jest bezpieczne, itp. Jedynym problemem jest to, że prawdopodobnie i tak jest wolniejsze niż zwracanie wartości!
źródło
Po prostu utwórz obiekt i zwróć go
Myślę, że zrobisz sobie przysługę, jeśli zapomnisz o optymalizacji i po prostu napiszesz czytelny kod (będziesz musiał później uruchomić profiler - ale nie optymalizuj wstępnie).
źródło
Thing thing();
deklaruje funkcję lokalną i zwraca plikThing
.Po prostu zwróć obiekt taki jak ten:
Spowoduje to wywołanie konstruktora kopiującego w Things, więc możesz chcieć wykonać własną implementację tego. Lubię to:
Może to działać trochę wolniej, ale może w ogóle nie stanowić problemu.
Aktualizacja
Kompilator prawdopodobnie zoptymalizuje wywołanie konstruktora kopiującego, więc nie będzie dodatkowego narzutu. (Jak Dreamlax wskazał w komentarzu).
źródło
Thing thing();
deklaruje lokalną funkcję zwracającą aThing
, ponadto standard zezwala kompilatorowi na pominięcie konstruktora kopiującego w przedstawionym przypadku; każdy nowoczesny kompilator prawdopodobnie to zrobi.Czy próbowałeś użyć inteligentnych wskaźników (jeśli Rzecz jest naprawdę dużym i ciężkim obiektem), takich jak auto_ptr:
źródło
auto_ptr
s są przestarzałe; użyjshared_ptr
lubunique_ptr
zamiast.Jednym z szybkich sposobów ustalenia, czy wywoływany jest konstruktor kopiujący, jest dodanie logowania do konstruktora kopiującego Twojej klasy:
Zadzwoń
someFunction
; liczba wierszy „Kopiuj konstruktor został nazwany”, które otrzymasz, będzie się wahać między 0, 1 i 2. Jeśli żadne nie otrzymasz, oznacza to, że kompilator zoptymalizował zwracaną wartość out (co jest dozwolone). Jeśli masz nie dostać 0, a konstruktor kopia jest absurdalnie drogie, a następnie szukać alternatywnych sposobów, aby powrócić instancji od swoich funkcji.źródło
Po pierwsze masz błąd w kodzie, który chcesz mieć
Thing *thing(new Thing());
i tylkoreturn thing;
.shared_ptr<Thing>
. Deref go, ponieważ był to wskaźnik. Zostanie on usunięty, gdy ostatnie odwołanie doThing
zawartego wyjdzie poza zakres.źródło
Jestem pewien, że ekspert C ++ przyjdzie z lepszą odpowiedzią, ale osobiście podoba mi się drugie podejście. Używanie inteligentnych wskaźników pomaga w rozwiązaniu problemu zapominania o
delete
i, jak mówisz, wygląda to lepiej niż konieczność tworzenia obiektu przed ręką (i nadal konieczność usuwania go, jeśli chcesz go przydzielić na stercie).źródło