Suche…


Einführung

In vielen Situationen ist es nützlich, mehrere Werte aus einer Funktion zurückzugeben: Wenn Sie beispielsweise einen Artikel eingeben und Preis und Anzahl auf Lager zurückgeben möchten, kann diese Funktion hilfreich sein. Es gibt viele Möglichkeiten, dies in C ++ zu tun, und die meisten beinhalten die STL. Wenn Sie die STL jedoch aus irgendeinem Grund vermeiden möchten, gibt es noch verschiedene Möglichkeiten, einschließlich structs/classes und arrays .

Ausgabeparameter verwenden

Parameter können verwendet werden, um einen oder mehrere Werte zurückzugeben. Diese Parameter müssen nicht const Zeiger oder Verweise sein.

Verweise:

void calculate(int a, int b, int& c, int& d, int& e, int& f) {
    c = a + b;
    d = a - b;
    e = a * b;
    f = a / b;
}

Zeiger:

void calculate(int a, int b, int* c, int* d, int* e, int* f) {
    *c = a + b;
    *d = a - b;
    *e = a * b;
    *f = a / b;
}

Einige Bibliotheken oder Frameworks verwenden ein leeres 'OUT' #define , um deutlich zu machen, welche Parameter in der Funktionssignatur Ausgabeparameter sind. Dies hat keine funktionalen Auswirkungen und wird kompiliert, macht aber die Funktionssignatur etwas klarer.

#define OUT

void calculate(int a, int b, OUT int& c) {
    c = a + b;
}

Verwenden von std :: tuple

C ++ 11

Der Typ std::tuple kann eine beliebige Anzahl von Werten, die möglicherweise Werte verschiedener Typen enthalten, in einem einzigen Rückgabeobjekt bündeln:

std::tuple<int, int, int, int> foo(int a, int b) { // or auto (C++14)
   return std::make_tuple(a + b, a - b, a * b, a / b);
}

In C ++ 17 kann eine geschweifte Initialisierungsliste verwendet werden:

C ++ 17
std::tuple<int, int, int, int> foo(int a, int b)    {
    return {a + b, a - b, a * b, a / b};
}

Das Abrufen von Werten aus dem zurückgegebenen tuple kann mühsam sein und erfordert die Verwendung der Vorlagenfunktion std::get :

auto mrvs = foo(5, 12);
auto add = std::get<0>(mrvs);
auto sub = std::get<1>(mrvs);
auto mul = std::get<2>(mrvs);
auto div = std::get<3>(mrvs);

Wenn die Typen deklariert werden können, bevor die Funktion zurückkehrt, kann mit std::tie ein tuple in vorhandene Variablen entpackt werden:

int add, sub, mul, div;
std::tie(add, sub, mul, div) = foo(5, 12);

Wenn einer der zurückgegebenen Werte nicht benötigt wird, kann std::ignore verwendet werden:

std::tie(add, sub, std::ignore, div) = foo(5, 12);
C ++ 17

Strukturierte Bindungen können verwendet werden, um std::tie zu vermeiden:

auto [add, sub, mul, div] = foo(5,12);

Wenn Sie ein Tupel von lvalue-Referenzen anstelle eines Tupels von Werten zurückgeben möchten, verwenden Sie std::tie anstelle von std::make_tuple .

std::tuple<int&, int&> minmax( int& a, int& b ) {
  if (b<a)
    return std::tie(b,a);
  else
    return std::tie(a,b);
}

was erlaubt

void increase_least(int& a, int& b) {
  std::get<0>(minmax(a,b))++;
}

In seltenen Fällen verwenden Sie std::forward_as_tuple anstelle von std::tie ; Seien Sie vorsichtig, wenn Sie dies tun, da Provisorien möglicherweise nicht lange genug dauern, um aufgebraucht zu werden.

Verwenden von std :: array

C ++ 11

Der Container std::array kann eine feste Anzahl von Rückgabewerten zusammenfassen. Diese Nummer muss zur Kompilierzeit bekannt sein und alle Rückgabewerte müssen vom selben Typ sein:

std::array<int, 4> bar(int a, int b) {
    return { a + b, a - b, a * b, a / b };
}

Dies ersetzt Arrays vom Stil der Form int bar[4] . Der Vorteil ist, dass jetzt verschiedene c++ std-Funktionen verwendet werden können. Es bietet auch nützliche Member - Funktionen wie at dem eine sichere Mitglied Zugriffsfunktion mit gebundener Prüfung ist, und eine size , die die Größe des Arrays ohne Berechnung zurückkehren kann.

Verwenden von std :: pair

Das struct template std::pair kann genau zwei Rückgabewerte eines beliebigen Typs zusammenfassen:

#include <utility>
std::pair<int, int> foo(int a, int b) {
    return std::make_pair(a+b, a-b);
}

Mit C ++ 11 oder höher kann anstelle von std::make_pair eine Initialisierungsliste verwendet werden:

C ++ 11
#include <utility>
std::pair<int, int> foo(int a, int b) {
    return {a+b, a-b};
}

Die einzelnen Werte des zurückgegebenen std::pair können mit den first und second std::pair abgerufen werden:

std::pair<int, int> mrvs = foo(5, 12);
std::cout << mrvs.first + mrvs.second << std::endl;

Ausgabe:

10

Struct verwenden

Eine struct kann verwendet werden, um mehrere Rückgabewerte zu bündeln:

C ++ 11
struct foo_return_type {
    int add;
    int sub;
    int mul;
    int div;
};

foo_return_type foo(int a, int b) {
    return {a + b, a - b, a * b, a / b};
}

auto calc = foo(5, 12);
C ++ 11

Anstelle der Zuordnung zu einzelnen Feldern kann ein Konstruktor verwendet werden, um das Erstellen von zurückgegebenen Werten zu vereinfachen:

struct foo_return_type {
    int add;
    int sub;
    int mul;
    int div;
    foo_return_type(int add, int sub, int mul, int div)
    : add(add), sub(sub), mul(mul), div(div) {}
};

foo_return_type foo(int a, int b) {
     return foo_return_type(a + b, a - b, a * b, a / b);
}

foo_return_type calc = foo(5, 12);

Die einzelnen Ergebnisse, die von der Funktion foo() können durch Zugriff auf die Member-Variablen des struct calc abgerufen werden:

std::cout << calc.add << ' ' << calc.sub << ' ' << calc.mul << ' ' << calc.div << '\n';

Ausgabe:

17-7 60 0

Hinweis: Wenn Sie eine struct , werden die zurückgegebenen Werte in einem einzigen Objekt zusammengefasst und können mit aussagekräftigen Namen aufgerufen werden. Dies hilft auch, die Anzahl der überflüssigen Variablen zu reduzieren, die im Bereich der zurückgegebenen Werte erstellt werden.

C ++ 17

Um eine auszupacken struct aus einer Funktion, zurück strukturierte Bindungen können verwendet werden. Dadurch werden die Out-Parameter mit den In-Parametern auf eine Stufe gestellt:

int a=5, b=12;
auto[add, sub, mul, div] = foo(a, b);
std::cout << add << ' ' << sub << ' ' << mul << ' ' << div << '\n';

Die Ausgabe dieses Codes ist identisch mit der obigen. Die struct wird weiterhin verwendet, um die Werte aus der Funktion zurückzugeben. Dadurch können Sie die Felder individuell bearbeiten.

Strukturierte Bindungen

C ++ 17

C ++ 17 führt strukturierte Bindungen ein, die den Umgang mit mehreren Rückgabetypen noch einfacher machen, da Sie sich nicht auf std::tie() oder manuelles Tupel-Auspacken verlassen müssen:

std::map<std::string, int> m;

// insert an element into the map and check if insertion succeeded
auto [iterator, success] = m.insert({"Hello", 42});

if (success) {
    // your code goes here
}

// iterate over all elements without having to use the cryptic 'first' and 'second' names
for (auto const& [key, value] : m) {
    std::cout << "The value for " << key << " is " << value << '\n';
}

Strukturierte Bindungen können standardmäßig mit std::pair , std::tuple und einem beliebigen Typ verwendet werden, dessen nicht statische Datenmitglieder entweder öffentliche direkte Mitglieder oder Mitglieder einer eindeutigen Basisklasse sind:

struct A { int x; };
struct B : A { int y; };
B foo();

// with structured bindings
const auto [x, y] = foo();

// equivalent code without structured bindings
const auto result = foo();
auto& x = result.x;
auto& y = result.y;

Wenn Sie Ihren Typ "tupelartig" machen, arbeitet er automatisch mit Ihrem Typ. Ein tuple-like ist ein Typ mit den entsprechenden tuple_size , tuple_element und get tuple_element :

namespace my_ns {
    struct my_type {
        int x;
        double d;
        std::string s;
    };
    struct my_type_view {
        my_type* ptr;
    };
}

namespace std {
    template<>
    struct tuple_size<my_ns::my_type_view> : std::integral_constant<std::size_t, 3>
    {};

    template<> struct tuple_element<my_ns::my_type_view, 0>{ using type = int; };
    template<> struct tuple_element<my_ns::my_type_view, 1>{ using type = double; };
    template<> struct tuple_element<my_ns::my_type_view, 2>{ using type = std::string; };
}

namespace my_ns {
    template<std::size_t I>
    decltype(auto) get(my_type_view const& v) {
        if constexpr (I == 0)
            return v.ptr->x;
        else if constexpr (I == 1)
            return v.ptr->d;
        else if constexpr (I == 2)
            return v.ptr->s;
        static_assert(I < 3, "Only 3 elements");
    }
}

das funktioniert jetzt:

my_ns::my_type t{1, 3.14, "hello world"};

my_ns::my_type_view foo() {
    return {&t};
}

int main() {
    auto[x, d, s] = foo();
    std::cout << x << ',' << d << ',' << s << '\n';
}

Verwenden eines Funktionsobjekt-Consumer

Wir können einen Verbraucher bereitstellen, der mit mehreren relevanten Werten aufgerufen wird:

C ++ 11
template <class F>
void foo(int a, int b, F consumer) {
    consumer(a + b, a - b, a * b, a / b);
}

// use is simple... ignoring some results is possible as well
foo(5, 12, [](int sum, int , int , int ){
    std::cout << "sum is " << sum << '\n';
});

Dies wird als "Weiterleitungsstil" bezeichnet .

Sie können eine Funktion, die ein Tupel zurückgibt, in eine Continuous Passing-Style-Funktion anpassen:

C ++ 17
template<class Tuple>
struct continuation {
  Tuple t;
  template<class F>
  decltype(auto) operator->*(F&& f)&&{
    return std::apply( std::forward<F>(f), std::move(t) );
  }
};
std::tuple<int,int,int,int> foo(int a, int b);

continuation(foo(5,12))->*[](int sum, auto&&...) {
  std::cout << "sum is " << sum << '\n';
};

wobei komplexere Versionen in C ++ 14 oder C ++ 11 beschreibbar sind.

Mit std :: vector

Ein std::vector kann nützlich sein, um eine dynamische Anzahl von Variablen desselben Typs zurückzugeben. Im folgenden Beispiel wird als Datentyp int verwendet, aber ein std::vector kann jeden beliebigen Typ enthalten, der trivial kopierbar ist:

#include <vector>
#include <iostream>

// the following function returns all integers between and including 'a' and 'b' in a vector
// (the function can return up to std::vector::max_size elements with the vector, given that
// the system's main memory can hold that many items)
std::vector<int> fillVectorFrom(int a, int b) {
    std::vector<int> temp;
    for (int i = a; i <= b; i++) {
        temp.push_back(i);
    }
    return temp;
}

int main() {    
    // assigns the filled vector created inside the function to the new vector 'v'
    std::vector<int> v = fillVectorFrom(1, 10);

    // prints "1 2 3 4 5 6 7 8 9 10 "
    for (int i = 0; i < v.size(); i++) {
        std::cout << v[i] << " ";
    }
    std::cout << std::endl;
    return 0;
}

Ausgabe-Iterator verwenden

Mehrere Werte desselben Typs können zurückgegeben werden, indem ein Ausgabe-Iterator an die Funktion übergeben wird. Dies ist besonders häufig bei generischen Funktionen (wie bei den Algorithmen der Standardbibliothek).

Beispiel:

template<typename Incrementable, typename OutputIterator>
void generate_sequence(Incrementable from, Incrementable to, OutputIterator output) {
    for (Incrementable k = from; k != to; ++k)
        *output++ = k;
}

Verwendungsbeispiel:

std::vector<int> digits;
generate_sequence(0, 10, std::back_inserter(digits));
// digits now contains {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow