c++

Zwracanie wielu wartości z funkcji C ++


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 iloraz i resztę. Jednym ze sposobów, który zwykle widzę, jest użycie parametrów referencyjnych:
void divide(int dividend, int divisor, int& quotient, int& remainder);

Odmiana powinna zwracać jedną wartość i przekazywać inną przez parametr odwołania:
int divide(int dividend, int divisor, int& remainder);

Innym sposobem byłoby zadeklarowanie struktury zawierającej wszystkie wyniki i zwrócenie jej:
struct divide_result {
int quotient;
int remainder;
};divide_result divide(int dividend, int divisor);

Czy jedna z tych metod jest ogólnie preferowana, czy też istnieją inne sugestie?
Edycja: w rzeczywistym kodzie mogą występować więcej niż dwa wyniki. Mogą też być różnego rodzaju.
Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Używam
std :: pair
(zwykle typedef'D), aby zwrócić dwie wartości. Powinieneś spojrzeć na
boost :: tuple
(istnieje
std :: tuple
w C ++ 11 i nowszych), aby zobaczyć więcej niż dwa wyniki.
Wraz z wprowadzeniem strukturalnego wiązania w C ++ 17, zwracanie
std :: tuple
powinno prawdopodobnie stać się akceptowanym standardem.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

W C ++ 11 możesz:
#include <tuple>std::tuple<int, int> divide(int dividend, int divisor) {
return std::make_tuple(dividend/divisor, dividend % divisor);
}#include <iostream>int main() {
using namespace std; int quotient, remainder; tie(quotient, remainder) = divide(14, 3); cout << quotient << ',' << remainder << endl;
}

W C ++ 17:
#include <tuple>std::tuple<int, int> divide(int dividend, int divisor) {
return {dividend/divisor, dividend % divisor};
}#include <iostream>int main() {
using namespace std; auto [quotient, remainder] = divide(14, 3); cout << quotient << ',' << remainder << endl;
}

lub ze strukturami:
auto divide(int dividend, int divisor) {
struct result {int quotient; int remainder;};
return result {dividend/divisor, dividend % divisor};
}#include <iostream>int main() {
using namespace std; auto result = divide(14, 3); cout << result.quotient << ',' << result.remainder << endl;// or auto [quotient, remainder] = divide(14, 3); cout << quotient << ',' << remainder << endl;
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Osobiście nie lubię zwracać parametrów z kilku powodów:
  • podczas wywoływania nie zawsze jest oczywiste, które parametry są wejściowe, a które wyjściowe
  • generalnie musisz utworzyć zmienną lokalną, aby złapać wynik, podczas gdy wartości zwracane mogą być używane w tekście (co może, ale nie musi być dobrym pomysłem, ale przynajmniej masz taką opcję)
  • wydaje mi się czystsze mieć funkcję „w drzwiach” i „na zewnątrz” - wszystkie wejścia idą tutaj, wszystkie wyjścia idą tam
  • Lubię, aby moja lista argumentów była jak najkrótsza

Mam również pewne zastrzeżenia dotyczące techniki pary/krotki. W większości przypadków nie ma naturalnego uporządkowania zwracanych wartości. W jaki sposób czytelnik kodu może wiedzieć, czy wynik. Najpierw jest czynnikiem, czy resztą? Wykonawca może zmienić kolejność, która łamie istniejący kod. Jest to szczególnie trudne, jeśli wartości są tego samego typu, więc nie zostanie wygenerowany żaden błąd kompilatora ani ostrzeżenie. W rzeczywistości te argumenty dotyczą również zwracanych parametrów.
Oto kolejny przykład kodu, tym razem trochę mniej trywialny:
pair<double,double> calculateResultingVelocity(double windSpeed, double windAzimuth,
double planeAirspeed, double planeCourse);pair<double,double> result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.first << endl;
cout << result.second << endl;

Czy to powoduje wydrukowanie prędkości względem ziemi i kursu, czy też kursu i prędkości względem ziemi? To nie jest oczywiste.
Porównaj z tym:
struct Velocity {
double speed;
double azimuth;
};
Velocity calculateResultingVelocity(double windSpeed, double windAzimuth,
double planeAirspeed, double planeCourse);Velocity result = calculateResultingVelocity(25, 320, 280, 90);
cout << result.speed << endl;
cout << result.azimuth << endl;

Myślę, że to jest wyraźniejsze niż jasne.
Więc myślę, że moim pierwszym wyborem w ogóle jest technika konstrukcji. W niektórych przypadkach idea pary/krotki jest prawdopodobnie świetnym rozwiązaniem. Chciałbym uniknąć zwracania parametrów, gdy tylko jest to możliwe.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

std::pair<int, int> divide(int dividend, int divisor)
{
// :
return std::make_pair(quotient, remainder);
}std::pair<int, int> answer = divide(5,2);
// answer.first == quotient
// answer.second == remainder

std :: pair to zasadniczo rozwiązanie strukturalne, ale już zdefiniowane dla Ciebie i gotowe do dostosowania do dowolnych dwóch typów danych.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Zależy to całkowicie od rzeczywistej funkcji i wartości zbioru wartości, a także ich rozmiarów:
  • Jeśli są ze sobą powiązane, jak w przykładzie twojej frakcji, użyłbym instancji struktury lub klasy.
  • Jeśli nie są one w rzeczywistości powiązane i nie można ich zgrupować w klasę/strukturę, być może powinieneś zmienić metodę na dwie części.
  • W zależności od rozmiaru zwracanych wartości w pamięci może być konieczne zwrócenie wskaźnika do wystąpienia klasy lub struktury albo użycie parametrów referencyjnych.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Rozwiązaniem OO w tym przypadku jest utworzenie klasy relacji. Nie wymagałoby to żadnego dodatkowego kodu (zaoszczędzić trochę), byłoby znacznie czystsze/bardziej przejrzyste i dałoby ci dodatkowe refaktoryzacje, które pozwolą ci wyczyścić kod spoza tej klasy.
Właściwie myślę, że ktoś zalecił zwrócenie struktury, która jest wystarczająco bliska, ale przesłania intencję, że powinna to być w pełni przemyślana klasa z konstruktorem i wieloma metodami, a właściwie ta „metoda”, o której wspomniałeś pierwotnie (jako zwracająca parę) powinna być najprawdopodobniej element członkowski tej klasy zwracający własną instancję.
Wiem, że twój przykład to po prostu „Przykład”, ale chodzi o to, że jeśli twoja funkcja nie robi dużo więcej niż powinna robić jakakolwiek funkcja, jeśli chcesz, aby zwracała wiele wartości, prawie na pewno brakuje ci obiektu.
Nie krępuj się tworzyć tych małych klas, aby wykonywać małe fragmenty pracy - to magia OO - kończysz ją na części, aż każda metoda będzie bardzo mała i prosta, a każda klasa będzie mała i prosta.
Jeszcze jedna rzecz, która powinna wskazywać, że coś było nie tak: w OO praktycznie nie masz danych - w OO nie chodzi o transfer danych, klasa musi wewnętrznie zarządzać własnymi danymi i manipulować nimi, każdy transfer danych (w tym dostęp do metod) jest znak, że być może trzeba będzie coś przemyśleć.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:


W C ++ 17 możesz również zwrócić jedną lub więcej ustalonych/niepodlegających kopiowaniu wartości

(w niektórych przypadkach). Możliwość zwracania stałych typów pochodzi z nowych optymalizacji gwarantowanych zwrotów i dobrze się z nimi łączy

kruszywa

i co można nazwać

konstruktorzy szablonów

.
template<typename T1,typename T2,typename T3>
struct many {
T1 a;
T2 b;
T3 c;
};// guide:
template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;auto f(){ return many{string(),5.7, unmovable()}; }; int main(){
// in place construct x,y,z with a string, 5.7 and unmovable.
auto [x,y,z] = f();
}

Najlepsze w tym jest to, że na pewno nie spowoduje

Nie

skopiuj lub przenieś. Możesz również zrobić przykład wariadyczny o
wielu
strukturach. Nowe szczegóły:
Zwracanie agregatów ze zmienną liczbą argumentów (strukturą) i składnią dla szablonu zmiennej C ++ 17 `` Przewodnik po odliczaniu konstrukcji ''
https://coderoad.ru/38393302/
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Istnieje precedens zwracania struktur w standardzie C (a tym samym C ++) z
div
,
ldiv
(oraz, w C99,
lldiv
) funkcje z
& < stdlib.h & >
(lub
& < cstdlib & >
).
„Mieszanka wartości zwracanej i parametrów zwracanych” jest zwykle najmniej czysta.
Posiadanie funkcji, która zwraca stan i zwraca dane poprzez parametry zwracane, ma sens w C; jest to mniej sensowne w C ++, gdzie zamiast tego można użyć wyjątków do przekazywania informacji o awarii.
Jeśli istnieje więcej niż dwie wartości zwracane, prawdopodobnie najlepiej jest użyć mechanizmu podobnego do struktury.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Użyj struktury lub klasy jako wartości zwracanej. Używanie
std :: pair
może teraz działać, ale
  • jest nieelastyczne, jeśli później zdecydujesz, że potrzebujesz więcej informacji.;
  • z deklaracji funkcji w nagłówku nie jest do końca jasne, co jest zwracane iw jakiej kolejności.

Zwrócenie struktury z samodokumentującymi się nazwami zmiennych składowych prawdopodobnie będzie mniej podatne na błędy dla osób używających Twojej funkcji. Zakładając na chwilę kapelusz kolegi, struktura
divide_result
jest dla mnie, potencjalnego użytkownika Twojej funkcji, łatwa do zrozumienia w ciągu 2 sekund. Majstrowanie przy parametrach wyjściowych lub tajemniczych parach i krotkach zajmie więcej czasu do odczytania i może być niewłaściwie użyte. I najprawdopodobniej nawet po kilkukrotnym użyciu funkcji nadal nie pamiętam prawidłowej kolejności argumentów.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Jeśli twoja funkcja zwraca wartość przez odniesienie, kompilator nie może przechowywać jej w rejestrze, gdy wywoływane są inne funkcje, ponieważ teoretycznie pierwsza funkcja może przechowywać adres zmiennej przekazanej do niej w globalnie dostępnej zmiennej, a wszelkie późniejsze funkcje może to zmienić, więc kompilator będzie musiał (1) zapisać wartość z rejestrów z powrotem do pamięci przed wywołaniem innych funkcji i (2) ponownie odczytać ją, jeśli to konieczne, z pamięci po każdym takim wywołaniu.
Jeśli skorzystasz z linku, ucierpi na tym optymalizacja programu
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Tutaj piszę program, który zwraca wiele wartości (więcej niż dwie wartości) w c ++. Ten program działa w języku c ++ 14 (G ++ 4.9.2). program jest podobny do kalkulatora.
# include <tuple>
# include <iostream>using namespace std; tuple < int,int,int,int,int > cal(int n1, int n2)
{
return make_tuple(n1/n2,n1%n2,n1+n2,n1-n2,n1*n2);
}int main()
{
int qut,rer,add,sub,mul,a,b;
cin>>a>>b;
tie(qut,rer,add,sub,mul)=cal(a,b);
cout << "quotient= "<<qut<<endl;
cout << "remainder= "<<rer<<endl;
cout << "addition= "<<add<<endl;
cout << "subtraction= "<<sub<<endl;
cout << "multiplication= "<<mul<<endl;
return 0;
}

W ten sposób możesz jasno zrozumieć, że w ten sposób możesz zwrócić wiele wartości z funkcji. podczas używania std :: pair można zwrócić tylko 2 wartości, a std :: tuple może zwrócić więcej niż dwie wartości.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Istnieje wiele sposobów zwracania wielu parametrów. Idę wydychać powietrze.
Użyj parametrów odniesienia:
void foo( int& result, int& other_result );

użyj parametrów wskaźnika:
void foo( int* result, int* other_result );

co ma tę zaletę, że musisz
& amp;
na stronie wywołania, być może ostrzegając ludzi, że jest to parametr out.
Napisz szablon i użyj go:
template<class T>
struct out {
std::function<void(T)> target;
out(T* t):target([t](T&& in){ *t = std::move(in); }) {}
out(std::aligned_storage_t<sizeof(T), alignof(T)>* t):
target([t](T&& in){ ::new( (void*)t ) T(std::move(in)); } ) {}
template<class...Args>
void emplace(Args&&...args) {
target( T(std::forward<Args>(args)...) );
}
template<class X>
void operator=(X&&x){ emplace(std::forward<X>(x)); }
template<class...Args>
void operator()(Args...&&args){ emplace(std::forward<Args>(args)...); }
};

wtedy możemy to zrobić:
void foo( out<int> result, out<int> other_result )

i to wszystko dobrze.
foo
nie może już czytać żadnej wartości przekazanej jako bonus.
Do wykreślenia
out
można użyć innych sposobów zdefiniowania punktu, w którym można umieścić dane. Na przykład oddzwonienie do umieszczenia gdzieś rzeczy.
Możemy zwrócić konstrukcję:
struct foo_r { int resu< int other_resu< };
foo_r foo();

whick działa dobrze w każdej wersji C ++ oraz w

c++17
https://coderoad.ru/?tag=c%2b%2b17
umożliwia również:
auto&&[result, other_result]=foo();

bez kosztów. Parametry nie mogą być nawet zmieniane dzięki gwarantowanej elizji.
Moglibyśmy zwrócić
std :: tuple
:
std::tuple<int, int> foo();

co ma tę wadę, że parametry nie są nazywane. Pozwala to na użycie

c++17
https://coderoad.ru/?tag=c%2b%2b17
:
auto&&[result, other_result]=foo();

również. Przed

c++17
https://coderoad.ru/?tag=c%2b%2b17
zamiast tego możemy zrobić:
int result, other_resu<
std::tie(result, other_result) = foo();

co jest jeszcze bardziej krępujące. Jednak gwarantowana Elicia nie działa tutaj.
Gdy wjedziemy na obce terytorium (a to po
out & < & >
!), Możemy skorzystać z kontynuacji przechodząc przez styl:
void foo( std::function<void(int result, int other_result)> );

a teraz dzwoniący robią to:
foo( [&](int result, int other_result) {
/* code */
} );

Zaletą tego stylu jest to, że możesz zwrócić dowolną liczbę wartości (z jednolitym typem) bez konieczności zarządzania pamięcią:
void get_all_values( std::function<void(int)> value )

wywołanie zwrotne
value
można wywołać 500 razy, gdy
get_all_values ​​([& amp;] (int value) {})
.
W przypadku czystego szaleństwa możesz nawet użyć kontynuacji w sequelu.
void foo( std::function<void(int, std::function<void(int)>)> result );

którego korzyść wygląda następująco:
foo( [&](int result, auto&& other){ other([&](int other){
/* code */
}) });

co spowodowałoby ustanowienie relacji wiele-jeden między
result
a
other
.
Ponownie, jako wartość dla Ciebie, możemy to zrobić:
void foo( std::function< void(span<int>) > results )

tutaj wywołujemy oddzwonienie z szeregiem wyników. Możemy to robić nawet wielokrotnie.
Używając tego, możesz mieć funkcję, która wydajnie przesyła megabajty danych bez dokonywania alokacji ze stosu.
void foo( std::function< void(span<int>) > results ) {
int local_buffer[1024];
std::size_t used = 0;
auto send_data=[&]{
if (!used) return;
results({ local_buffer, used });
used = 0;
};
auto add_datum=[&](int x){
local_buffer[used] = x;
++used;
if (used == 1024) send_data();
};
auto add_data=[&](gsl::span<int const> xs) {
for (auto x:xs) add_datum(x);
};
for (int i = 0; i < 7+(1<<20); ++i) {
add_datum(i);
}
send_data();// any leftover
}

Teraz
std :: function
jest do tego trochę ciężkie, ponieważ będziemy to robić w środowiskach o zerowym narzutu bez przydzielania zasobów. Dlatego chcielibyśmy mieć
function_view
https://foonathan.net/blog/201 ... .html
który nigdy się nie wyróżnia.
Jest inne rozwiązanie:
std::function<void(std::function<void(int result, int other_result)>)> foo(int input);

gdzie zamiast pobierać callback i wywoływać go,
foo
zamiast tego zwraca funkcję, która akceptuje callback.
foo (7) ([& amp;] (int wynik, int inny_wynik) {/ * kod */});
umożliwia to oddzielenie parametrów wyjściowych od parametrów wejściowych oddzielnymi nawiasami.
Z
variant
coroutines i

c++20
https://coderoad.ru/?tag=c%2b%2b20
możesz sprawić, że
foo
będzie generatorem typu zwracanego wariantu (lub po prostu typu zwracanego). Składnia nie została jeszcze poprawiona, więc nie podam przykładów.
W świecie sygnałów i slotów funkcja zapewniająca zestaw sygnałów:
template<class...Args>
struct broadcaster;broadcaster<int, int> foo();

pozwala stworzyć
foo
, który działa asynchronicznie i przekazuje wynik po zakończeniu.
W tej linii mamy wiele potokowych metod, w których funkcja nie robi czegoś, ale raczej organizuje dane, które muszą być w jakiś sposób połączone, i jest to stosunkowo niezależna akcja.
foo( int_source )( int_dest1, int_dest2 );

to ten kod to nic

robi

dopóki
int_source
nie będzie zawierał liczb całkowitych, aby to podać. W takim przypadku wyniki zaczną uzyskiwać wyniki
int_dest1
i
int_dest2
.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Zwykle używam wartości wyjściowych w funkcjach takich jak ta, ponieważ mam paradygmat funkcji zwracania sukcesu/błędu i lubię utrzymywać spójność.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Alternatywy obejmują tablice,

generatory
http://en.wikipedia.org/wiki/Generator_(computer_science)
i

odwrócenie sterowania
http://en.wikipedia.org/wiki/Inversion_of_Control
ale żaden z nich nie pasuje tutaj.
Niektóre (jak Microsoft w historycznym Win32) mają tendencję do używania parametrów referencyjnych dla uproszczenia, ponieważ jest jasne, kto przydziela i jak będzie wyglądać na stosie, zmniejsza mnożenie się struktur i umożliwia oddzielną wartość zwracaną dla sukcesu.
Programiści „czystej” preferują strukturę, zakładając, że tak jest
wartość funkcji (jak w tym przypadku), a nie to, na co funkcja wpływa przypadkowo. Gdybyś miał bardziej złożoną procedurę lub coś ze stanem, prawdopodobnie użyłbyś referencji (zakładając, że masz powód, aby nie używać tej klasy).
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Powiedziałbym, że nie ma preferowanej metody, wszystko zależy od tego, co zrobisz z odpowiedzią. Jeśli wyniki zostaną użyte razem w dalszym przetwarzaniu, to struktury mają sens, jeśli nie, to staram się przekazywać je jako oddzielne odwołania, chyba że funkcja zostanie użyta w instrukcji złożonej:
x = divide( x, y, z ) + divide( a, b, c );
Często wolę przekazywać „struktury” przez odniesienie na liście parametrów, zamiast mieć narzut kopiowania przy zwracaniu nowej struktury (ale to się przejmuje drobiazgami).
void divide(int dividend, int divisor, Answer &ans)
Czy nasze parametry są mylące? Parametr wysłany jako odwołanie zakłada, że ​​wartość ulegnie zmianie (w przeciwieństwie do odwołania do stałej). Rozsądne nazewnictwo usuwa również zamieszanie.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Dlaczego nalegasz na funkcję z wieloma zwracanymi wartościami? Z OOP można użyć klasy, która oferuje zwykłą funkcję z jedną wartością zwracaną i dowolną liczbą dodatkowych „wartości zwracanych”, jak pokazano poniżej. Zaletą jest to, że wywołujący ma możliwość spojrzenia na dodatkowe elementy danych, ale nie jest to wymagane. Jest to preferowana metoda w przypadku złożonych wywołań baz danych lub sieci, w przypadku których może być wymagane zwrócenie wielu dodatkowych informacji w przypadku błędów.
Aby odpowiedzieć na Twoje pierwotne pytanie, w tym przykładzie zastosowano metodę, która zwraca czynnik, którego może potrzebować większość wywołujących, a ponadto po wywołaniu metody można pobrać pozostałą część jako element danych.
class div{
public:
int remainder; int quotient(int dividend, int divisor){
remainder = ...;
return ...;
}
};
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

zamiast zwracać wiele wartości, po prostu zwróć jedną z nich i odwołaj się do innych w wymaganej funkcji, np .:
int divide(int a,int b,int quo,int &rem)
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Zwiększona krotka byłaby moim preferowanym wyborem dla systemu ogólnego zwracającego więcej niż jedną wartość z funkcji.
Możliwy przykład:
include "boost/tuple/tuple.hpp"tuple <int,int> divide( int dividend,int divisor ) {
return make_tuple(dividend/divisor,dividend % divisor )
}
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Możemy zadeklarować funkcję zwracającą zmienną typu strukturalnego zdefiniowaną przez użytkownika lub wskaźnik do niej. Dzięki właściwości struktury wiemy, że struktura w C może zawierać kilka wartości typów asymetrycznych (na przykład jedną zmienną typu int, cztery zmienne typu char, dwie zmienne typu float itd.)
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Zrobiłbym to przez odniesienie, jeśli jest to tylko kilka wartości zwracanych, ale w przypadku bardziej złożonych typów można to również zrobić w następujący sposób:
static struct SomeReturnType {int a,b,c; string str;} SomeFunction()
{
return {1,2,3,string("hello world")};// make sure you return values in the right order!
}

użyj „static”, aby ograniczyć zakres zwracanego typu do tej jednostki kompilacji, jeśli dotyczy on tylko tymczasowego zwracanego typu.
SomeReturnType st = SomeFunction();
cout << "a " << st.a << endl;
cout << "b " << st.b << endl;
cout << "c " << st.c << endl;
cout << "str " << st.str << endl;

To zdecydowanie nie jest najmilszy sposób na zrobienie tego, ale zadziała.

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się