Czy istnieje preferowany sposób zwracania wielu wartości z funkcji C ++? Na przykład wyobraź sobie funkcję, która dzieli dwie liczby całkowite i zwraca zarówno iloraz, jak i resztę. Jednym ze sposobów, które często widzę, jest użycie parametrów odniesienia:
void divide(int dividend, int divisor, int& quotient, int& remainder);
Odmianą jest zwrócenie jednej wartości i przekazanie drugiej przez parametr referencyjny:
int divide(int dividend, int divisor, int& remainder);
Innym sposobem byłoby zadeklarowanie struktury zawierającej wszystkie wyniki i zwrócenie:
struct divide_result {
int quotient;
int remainder;
};
divide_result divide(int dividend, int divisor);
Czy jeden z tych sposobów jest ogólnie preferowany, czy są też inne sugestie?
Edycja: w kodzie rzeczywistym mogą występować więcej niż dwa wyniki. Mogą być również różnego rodzaju.
std::tuple
.std::tie
stackoverflow.com/a/2573822/502144W C ++ 11 możesz:
W C ++ 17:
lub z structs:
źródło
struct
linię poza ciało funkcji i zastąpićauto
return funkcjiresult
.divide
w osobnym pliku CPP? Dostaję błąderror: use of ‘auto divide(int, int)’ before deduction of ‘auto’
. Jak to rozwiązać?Osobiście nie lubię zwracanych parametrów z wielu powodów:
Mam też pewne zastrzeżenia do techniki pary / krotki. Zasadniczo często nie ma naturalnego porządku wartości zwracanych. Skąd czytelnik kodu wie, czy wynik. Pierwszy jest ilorazem, czy resztą? I implementator może zmienić kolejność, co zepsuje istniejący kod. Jest to szczególnie podstępne, jeśli wartości są tego samego typu, aby nie był generowany błąd kompilatora ani ostrzeżenie. W rzeczywistości argumenty te dotyczą również parametrów zwracanych.
Oto kolejny przykład kodu, ten nieco mniej trywialny:
Czy to drukuje prędkość i kurs, czy też kurs i prędkość? To nie jest oczywiste.
Porównaj z tym:
Myślę, że to jest jaśniejsze.
Myślę więc, że moim pierwszym wyborem jest technika struct. Pomysł pary / krotki jest prawdopodobnie świetnym rozwiązaniem w niektórych przypadkach. Chciałbym uniknąć parametrów zwracanych, gdy to możliwe.
źródło
struct
polubieniaVelocity
jest miła. Jednak jedną z obaw jest to, że zanieczyszcza przestrzeń nazw. Podejrzewam, że w C ++ 11struct
może mieć długą nazwę typu i można jej użyćauto result = calculateResultingVelocity(...)
.struct { int a, b; } my_func();
. To może być stosowany jako to:auto result = my_func();
. Ale C ++ nie pozwala na to: „nowe typy nie mogą być zdefiniowane w typie zwracanym”. Więc muszę tworzyć struktury takie jakstruct my_func_result_t
...auto
, więcauto result = my_func();
jest łatwo dostępny.std :: pair jest zasadniczo Twoim rozwiązaniem strukturalnym, ale już dla Ciebie zdefiniowanym i gotowym do dostosowania do dowolnych dwóch typów danych.
źródło
Jest to całkowicie zależne od faktycznej funkcji i znaczenia wielu wartości oraz ich rozmiarów:
źródło
Rozwiązaniem OO w tym celu jest utworzenie klasy współczynnika. Nie wymagałoby to żadnego dodatkowego kodu (pozwoliłoby zaoszczędzić trochę), byłoby znacznie czystsze / jaśniejsze i dałoby ci dodatkowe refaktoryzacje pozwalające ci oczyścić kod również poza tą klasą.
Właściwie myślę, że ktoś zalecił zwrócenie struktury, która jest wystarczająco blisko, ale ukrywa zamiar, że musi to być w pełni przemyślana klasa z konstruktorem i kilkoma metodami, w rzeczywistości „metodą”, o której wspominałeś (jako zwracaniem para) najprawdopodobniej powinien być członkiem tej klasy zwracającym instancję samego siebie.
Wiem, że twój przykład był po prostu „Przykładem”, ale faktem jest, że jeśli twoja funkcja nie robi o wiele więcej niż jakakolwiek funkcja powinna robić, jeśli chcesz, aby zwracała wiele wartości, prawie na pewno brakuje obiektu.
Nie bój się tworzyć tych małych klas, aby wykonywać małe prace - to magia OO - w końcu rozkładasz ją, dopóki każda metoda nie będzie bardzo mała i prosta, a każda klasa mała i zrozumiała.
Kolejna rzecz, która powinna wskazywać, że coś było nie tak: w OO zasadniczo nie masz danych - OO nie polega na przekazywaniu danych, klasa musi wewnętrznie zarządzać swoimi własnymi danymi i manipulować nimi, wszelkie przekazywane dane (w tym akcesoria) jest znakiem, że może trzeba coś przemyśleć
źródło
Istnieje precedens dla zwracania struktur w standardzie C (a stąd C ++) z funkcjami
div
,ldiv
(i, w C99,lldiv
) z<stdlib.h>
(lub<cstdlib>
).„Połączenie wartości zwracanej i parametrów zwracanych” jest zwykle najmniej czyste.
Posiadanie funkcji zwracającej status i zwracającej dane poprzez parametry zwracane jest sensowne w C; jest to mniej oczywiste w C ++, gdzie zamiast tego można użyć wyjątków do przekazywania informacji o awarii.
Jeśli są więcej niż dwie zwracane wartości, prawdopodobnie mechanizm podobny do struktury jest prawdopodobnie najlepszy.
źródło
W C ++ 17 możesz również zwrócić jedną lub więcej wartości, których nie można przenieść / których nie można skopiować (w niektórych przypadkach). Możliwość zwracania typów nieprzenośnych pochodzi z nowej optymalizacji gwarantowanej wartości zwrotu i ładnie komponuje się z agregatami oraz konstruktorami szablonowymi .
Ładna rzeczą jest to, że jest zagwarantowane, aby nie powodowały jakiegokolwiek kopiowania lub przenoszenia. Możesz także utworzyć przykładową
many
strukturę variadic. Więcej szczegółów:Zwracane agregaty variadic (struct) i składnia dla szablonu variadic C ++ 17 „przewodnik po dedukcji konstrukcji”
źródło
Istnieje wiele sposobów zwracania wielu parametrów. Będę wyczerpany.
Użyj parametrów referencyjnych:
użyj parametrów wskaźnika:
co ma tę zaletę, że musisz to zrobić
&
witrynie wywołującej, prawdopodobnie ostrzegając ludzi, że jest to parametr sparametryzowany.Napisz szablon i użyj go:
wtedy możemy zrobić:
i wszystko jest dobrze.
foo
nie jest już w stanie odczytać żadnej wartości przekazanej jako bonus.Do budowy można wykorzystać inne sposoby definiowania miejsca, w którym można umieścić dane
out
. Na przykład wywołanie zwrotne, aby umieścić gdzieś różne rzeczy.Możemy zwrócić strukturę:
whick działa dobrze w każdej wersji C ++ i c ++ 17 pozwala to również:
przy zerowym koszcie. Parametry nie mogą nawet zostać przeniesione dzięki gwarantowanemu elekcji.
Możemy zwrócić
std::tuple
:co ma tę wadę, że parametry nie są nazwane. To pozwalac ++ 17:
także. Przedc ++ 17 zamiast tego możemy zrobić:
co jest nieco bardziej niezręczne. Gwarantowane wybranie jednak tutaj nie działa.
Wchodząc na nieznane terytorium (i to jest po
out<>
!), Możemy użyć stylu przekazywania kontynuacji:a teraz dzwoniący wykonują:
zaletą tego stylu jest to, że możesz zwrócić dowolną liczbę wartości (o jednolitym typie) bez konieczności zarządzania pamięcią:
value
zwrotna można nazwać 500 chwile, kiedyget_all_values( [&](int value){} )
.Dla czystego szaleństwa możesz nawet użyć kontynuacji kontynuacji.
którego użycie wygląda następująco:
co pozwoliłoby na wiele relacji między
result
iother
.Ponownie z wartościami Uniforn możemy to zrobić:
tutaj nazywamy callback z szerokim zakresem wyników. Możemy to zrobić nawet wielokrotnie.
Korzystając z tego, możesz mieć funkcję, która skutecznie przekazuje megabajty danych bez dokonywania alokacji poza stos.
Teraz
std::function
jest to trochę trudne, ponieważ robilibyśmy to w środowiskach zero-narzutowych bez alokacji. Chcielibyśmy więc takiego,function_view
który nigdy nie przydziela.Innym rozwiązaniem jest:
gdzie zamiast odbierać oddzwonienie i wywoływać je,
foo
zamiast tego zwraca funkcję, która odbiera oddzwonienie.foo (7) ([&] (int wynik, int other_result) {/ * kod * /}); powoduje to przerwanie parametrów wyjściowych od parametrów wejściowych poprzez posiadanie oddzielnych nawiasów.
Z
variant
ic ++ 20coroutines, możesz stworzyćfoo
generator wariantu typów zwracanych (lub po prostu typu zwracanych). Składnia nie jest jeszcze naprawiona, więc nie podam przykładów.W świecie sygnałów i gniazd funkcja ujawniająca zestaw sygnałów:
pozwala utworzyć
foo
asynchronizujący i rozgłaszający wynik po zakończeniu.W dalszej części tej linii mamy różne techniki potokowe, w których funkcja nie robi nic, ale raczej organizuje połączenie danych w jakiś sposób, a działanie jest względnie niezależne.
wtedy ten kod nic nie robi , dopóki
int_source
nie zostaną podane liczby całkowite. Kiedy to zrobiint_dest1
iint_dest2
zacznij otrzymywać wyniki.źródło
auto&&[result, other_result]=foo();
funkcjach zwracających zarówno krotki, jak i struktury. Dzięki!Użyj struktury lub klasy jako wartości zwracanej. Korzystanie
std::pair
może na razie działać, aleZwrócenie struktury z samodokumentującymi nazwami zmiennych członków prawdopodobnie będzie mniej podatne na błędy dla każdego, kto używa twojej funkcji. Zakładając na chwilę mój kapelusz współpracownika, twoja
divide_result
struktura jest dla mnie, potencjalnego użytkownika twojej funkcji, łatwa do zrozumienia po 2 sekundach. Bałagan z parametrami wyjścia lub tajemniczymi parami i krotkami zajęłoby więcej czasu na odczytanie i może być używany nieprawidłowo. I najprawdopodobniej nawet po kilkukrotnym użyciu funkcji nadal nie będę pamiętał poprawnej kolejności argumentów.źródło
Jeśli funkcja zwraca wartość przez odwołanie, kompilator nie może zapisać jej w rejestrze podczas wywoływania innych funkcji, ponieważ teoretycznie pierwsza funkcja może zapisać adres zmiennej przekazanej do niej w zmiennej globalnie dostępnej, a wszelkie funkcje wywoływane kolejno zmień go, aby kompilator (1) zapisał wartość z rejestrów z powrotem do pamięci przed wywołaniem innych funkcji i (2) ponownie ją odczytał, gdy będzie potrzebny z pamięci po każdym z takich wywołań.
Jeśli powrócisz przez odniesienie, optymalizacja twojego programu ucierpi
źródło
Tutaj piszę program, który zwraca wiele wartości (więcej niż dwie wartości) w c ++. Ten program jest wykonywalny w c ++ 14 (G ++ 4.9.2). program jest jak kalkulator.
Możesz więc wyraźnie zrozumieć, że w ten sposób możesz zwrócić wiele wartości z funkcji. przy użyciu std :: pair można zwrócić tylko 2 wartości, a std :: tuple może zwrócić więcej niż dwie wartości.
źródło
auto
typu return,cal
aby uczynić to jeszcze czystszym. (IMO).Często używam out-vals w funkcjach takich jak ta, ponieważ trzymam się paradygmatu funkcji zwracającej kody sukcesu / błędów i lubię utrzymywać jednolitość.
źródło
Alternatywy obejmują tablice, generatory i odwrócenie kontroli , ale żadna z nich nie jest tutaj odpowiednia.
Niektóre (np. Microsoft w historycznym Win32) zwykle używają parametrów referencyjnych dla uproszczenia, ponieważ jest jasne, kto przydziela i jak będzie wyglądał na stosie, zmniejsza mnożenie struktur i pozwala na osobną wartość zwrotną dla sukcesu.
„Czyści” programiści preferują strukturę, zakładając, że jest to wartość funkcji (jak w tym przypadku), niż coś, co przypadkowo dotknęło funkcja. Jeśli miałbyś bardziej skomplikowaną procedurę lub coś ze stanem, prawdopodobnie użyłbyś referencji (zakładając, że masz powód nieużywania klasy).
źródło
Powiedziałbym, że nie ma preferowanej metody, wszystko zależy od tego, co zrobisz z odpowiedzią. Jeśli wyniki będą używane razem w dalszym przetwarzaniu, wówczas struktury mają sens, jeśli nie, to raczej przekazują je jako indywidualne odwołania, chyba że funkcja będzie używana w instrukcji złożonej:
x = divide( x, y, z ) + divide( a, b, c );
Często wybieram przekazywanie „struktur” przez odniesienie na liście parametrów, zamiast narzutu na kopiowanie zwracania nowej struktury (ale to pocenie się drobiazgów).
void divide(int dividend, int divisor, Answer &ans)
Czy parametry są mylące? Parametr wysłany jako referencja sugeruje, że wartość się zmieni (w przeciwieństwie do stałej referencji). Rozsądne nazewnictwo również usuwa zamieszanie.
źródło
Dlaczego nalegasz na funkcję z wieloma zwracanymi wartościami? Dzięki OOP możesz użyć klasy oferującej regularną funkcję z pojedynczą wartością zwracaną i dowolną liczbą dodatkowych „wartości zwracanych”, jak poniżej. Zaletą jest to, że dzwoniący ma możliwość spojrzenia na dodatkowe elementy danych, ale nie jest to wymagane. Jest to preferowana metoda w przypadku skomplikowanych połączeń z bazą danych lub połączeniami sieciowymi, w których może być potrzebnych wiele dodatkowych informacji zwrotnych w przypadku wystąpienia błędów.
Aby odpowiedzieć na twoje pierwotne pytanie, w tym przykładzie jest metoda zwrócenia ilorazu, czego może potrzebować większość dzwoniących, a dodatkowo po wywołaniu metody resztę można uzyskać jako element danych.
źródło
zamiast zwracać wiele wartości, po prostu zwróć jedną z nich i odwołaj się do innych w wymaganej funkcji, na przykład:
źródło
Krotka zwiększająca byłaby moim preferowanym wyborem dla uogólnionego systemu zwracania więcej niż jednej wartości z funkcji.
Możliwy przykład:
źródło
Możemy zadeklarować funkcję tak, aby zwracała do niej zmienną zdefiniowaną przez użytkownika typu struktury lub wskaźnik. A dzięki właściwości struktury wiemy, że struktura w C może przechowywać wiele wartości typów asymetrycznych (tj. Jedną zmienną int, cztery zmienne char, dwie zmienne zmiennoprzecinkowe itd.)
źródło
Zrobiłbym to po prostu przez odniesienie, jeśli to tylko kilka zwracanych wartości, ale w przypadku bardziej złożonych typów możesz to po prostu zrobić w następujący sposób:
użyj „statyczny”, aby ograniczyć zakres typu zwracanego do tej jednostki kompilacji, jeśli ma to być tylko tymczasowy typ zwracany.
To zdecydowanie nie jest najładniejszy sposób na zrobienie tego, ale zadziała.
źródło
Oto pełny przykład tego rodzaju rozwiązania problemu
źródło