Zeiger/Referenzen zurückgeben

Betrachten wir folgendes Programm:

    #include <iostream>
    using namespace std;

    int* foo(int value)
    {
      int arr[2];
      arr[0]=value;
      arr[1]=-value;
      return arr;
    }

    int main()
    {
      int* p=foo(7);
      cout<<p[0]<<'\n'<<p[1]<<'\n';
    }
    

Was wird ausgegeben? Garnichts, denn wir haben hier undefiniertes verhalten. arr ist eine lokale Variable innerhalb von foo. Wir wissen, dass lokale Variablen beim Beenden der Funktion aufhören zu existieren. Somit hört auch arr auf zu existieren und p zeigt auf die Stelle, an der arr einmal war. Dies bedeutet: undefiniertes Verhalten, denn arr[0] und arr[1] existieren ja nicht mehr.

Merksatz!!Returne nie eine Referenz oder einen Zeiger auf eine lokale Variable.

Doch was können wir tun, wenn wir 2 Werte zurück geben wollen? Da bleibt zum einen die Möglichkeit std::pair zu verwenden:

    #include <iostream>
    #include <utility> //für pair
    using namespace std;

    pair<int, int> foo(int value)
    {
      pair<int, int> arr(value, -value);
      return arr;
      //oder einfach nur
      //return pair<int, int>(value, -value);
      //oder noch einfacher
      //return make_pair(value, -value);
    }

    int main()
    {
      pair<int, int> p=foo(7);
      cout<<p.first<<'\n'<<p.second<<'\n';
    }
    

pair ist ein Template - mehr dazu später. pair<int, int> ist ein Paar für 2 int. Statt int kann hier jeder Typ stehen - auch wieder pair. pair hat allerdings den Nachteil, dass die größe Fix ist und bei vielen Elemente eignet es sich auch nicht besonders gut: p.second.second.second.second.first;.

Man kann aber auch einen vector verwenden:

    #include <iostream>
    #include <vector> //für vector
    using namespace std;

    vector<int> foo(int value)
    {
      vector<int> arr;
      arr.push_back(value);
      arr.push_back(-value);
      return arr;
    }

    int main()
    {
      vector<int> p=foo(7);
      cout<<p[0]<<'\n'<<p[1]<<'\n';
    }
    

vector ist ebenfalls ein Template. int gibt hier wieder en Typen an. Ein vector ist ein dynamisches Array, aber mehr dazu gibt es erst später.

Hier hat vector den Nachteil, dass alle Elemente kopiert werden - bei 100.000 Elementen kann das ziemlich lang dauern.

Nun aber zu etwas simpleren Möglichkeiten:

    #include <iostream>
    using namespace std;

    int* foo(int value)
    {
      static int arr[2];
      arr[0]=value;
      arr[1]=-value;
      return arr;
    }

    int main()
    {
      int* p=foo(7);
      cout<<p[0]<<'\n'<<p[1]<<'\n';
    }
    

static Variablen bleiben auch nach dem Beenden der Funktion erhalten. Allerdings hat diese Variante einen gravierenden Nachteil:

    #include <iostream>
    using namespace std;

    int* foo(int value)
    {
      static int arr[2];
      arr[0]=value;
      arr[1]=-value;
      return arr;
    }

    int main()
    {
      int* p=foo(7);
      int* p2=foo(9);
      cout<<p[0]<<'\n'<<p[1]<<'\n';
      cout<<p2[0]<<'\n'<<p2[1]<<'\n';

    }
    

Beim zweiten Aufruf von foo bekommt arr einen anderen Wert und p und p2 zeigen auf die selben Werte. Es wird also zweimal 9 und -9 ausgegeben.

Jetzt beliben noch 2 Möglichkeiten mit dynamischen Speicher:

    #include <iostream>
    using namespace std;

    int* foo(int value)
    {
      int* arr=new int[2];
      arr[0]=value;
      arr[1]=-value;
      return arr;
    }

    int main()
    {
      int* p=foo(7);
      cout<<p[0]<<'\n'<<p[1]<<'\n';
      //delete [] p; //ups vergessen
    }
    

Wie wir sehen, kann man das delete [] p; leider viel zu leicht vergessen. Das bringt uns zu Variante Nummer 2:

    #include <iostream>
    using namespace std;

    void foo(int value, int* buffer)
    {
      buffer[0]=value;
      buffer[1]=-value;
    }

    int main()
    {
      int* p=new int[2];
      foo(7, p);
      cout<<p[0]<<'\n'<<p[1]<<'\n';
      delete [] p;
    }
    

Hier kann man das delete [] p; nicht mehr so leicht vergessen - da das new ja offen sichtbar ist.

In C verwendet man eher die letzte Variante, allerdings hängt dies stark vom Kontext ab. In C++ würde man eine Klasse schreiben, die das new/delete übernimmt. Später gibt es mehr dazu. Dies kann man entweder über Smart Pointer oder über eine eigene Array Klasse erreichen. Diese beiden Methoden werden wir später näher betrachten.

top