Zeiger als Parameter immer besser als vector?



  • Albert12 schrieb:

    ,
    ich habe eine ganz grundsätzliche Frage:

    Ist es immer besser einer Funktion einen Zeiger auf einen vector zu übergeben, anstatt den vector selbst?

    Nein. Es gibt sicher genug Fälle, wo es andersherum mehr Sinn macht.

    Albert12 schrieb:

    Also

    void foo(vector<int> *vec)
    

    ist doch eigentlich immer besser (von der Geschwindigkeit her) als

    void foo(vector<int> vec)
    

    oder?

    Schneller, ja. Aber es ist auch etwas völlig anderes. Die erste Funktion kann den Vektor des Aufrufers verändern. Die zweite Funktion hat ihre eigene Kopie des Vektors und Änderungen an dieser Kopie sind ohne weiteres nicht sichtbar nach außen.

    Eine dritte Variante wäre:

    void foo(vector<int> const& vec)
    

    Hier wird der Funktion nur eine Referenz übergeben, was so etwas ähnliches ist wie ein Zeiger. Allerdings kann und darf die Funktion das referenzierte Objekt wegen des "const"s nicht verändern (zumindest nicht über diese Referenz ohne const_cast). Man spart sich beim Aufruf den Adressoperator und die teure Kopie des Vektors.

    Was Du verwenden solltest, hängt wirklich davon ab, was Du eigentlich machen willst. Gerade seit C++2011 macht es in einigen Fällen mehr Sinn, auch wieder pass-by-value und return-by-value zuzulassen, da viele Typen extrem schnell "move-bar" sind (an einen neuen Speicherort schnell umziehen können); denn nicht immer ist eine Kopie von nöten. Beispiel:

    C++11:

    vector<int> umdrehen(vector<int> v) {
      reverse(v.begin(),v.end());
      return v;
    }
    
    int main() {
      vector<int> x = {1,2,3,5,8,13};
      x = umdrehen(move(x));
    }
    

    Hier wird nirgendswo etwas unnötig kopiert. Der Vektor zieht in den Scope der Funktion um, wird dort verändert und zieht dann quasi wieder zurück. So erlaubt C++2011 einen etwas funktionaleren Stil, ohne dass man sich mit Zeigern und Referenzen die Hände dreckig machen muss und ohne hohe Kosten bzgl Performanz.

    Anderes Beispiel:

    class person {
      string name_;
      int geburtsjahr_;
    public:
      person(string n, int gj) 
      : name_(move(n))
      , geburtsjahr_(gj)
      {}
    };
    

    Das 'n' wird ggf durch eine Kopie erzeugt. Da ein person-Objekt aber einen eigenen string speichert, muss irgendwo sowieso etwas kopiert werden. Das kann man hier dem Compiler bei der Erzeugunn von 'n' überlassen. Später zieht der String eben nach name_ um.

    Früher hätte man den Konstruktor wahrscheinlich so geschrieben:

    person(string const& n, int gj) 
      : name_(n)
      , geburtsjahr_(gj)
      {}
    

    Gut. Viel schlechter ist das jetzt auch nicht. Aber in einigen Fällen führt das noch zu einer unnötigen Kopie; denn hier wird explizit kopiert, da der Kopierkonstruktor direkt vom Programmierer aufgerufen wird. Das wäre aber gar nicht nötig in jeden Fall, z.b. wenn das Argument des Konstruktors nur ein temporärer String ist, der auch einfach "umziehen" könnte.

    Grundsätzlich: Soll die Funktion das übergebene Objekt nicht ändern und/oder nicht woanders hinkopieren, dann ist die Übergabe per ref-auf-const immer noch sinnvoll. Andernfalls ist ggf die Übergabe per value sinnvoll. Das kommt auch dann drauf an, ob der entsprechende Typ move-optimiert ist oder nicht.

    In generischem Code weiß man das nicht immer, ob ein Typ move-optimiert ist oder nicht. Da kann man dann in solchen Fällen z.B. mit "perfect forwarding" arbeiten:

    #define REQUIRES(...) ,class=typename std::enable_if<(__VA_ARGS__)>::type
    
    template<class A, class B>
    struct my_pair {
      A first;
      B second;
    
      my_pair() = default;
    
      template<class T, class U
        REQUIRES(   is_convertible<T,A>::value
                 && is_convertible<U,B>::value)
      >
      my_pair(T&& t, U&& u)
      : first(forward<T>(t))
      , second(forward<U>(u))
      {}
    };
    

    Was auch immer t und u sind, solange ich damit ein A bzw ein B erzeugen kann, wird das Argument direkt weitergeleitet.

    Cheers!
    kk



  • Albert12 schrieb:

    Ist es immer besser einer Funktion einen Zeiger auf einen vector zu übergeben, anstatt den vector selbst?

    Kommt drauf an. Es gibt zwei Fälle:
    - Die Funktion kopiert das Objekt sowieso -> Wertübergabe void foo(vector<int> vec)
    - Die Funktion kopiert das Objekt nicht (verwendet es nur vorübergehend oder speichert die Adresse des Objektes) -> Referenzübergabe void foo(const vector<int> &vec)



  • krümelkacker schrieb:

    ....

    C++11:

    vector<int> umdrehen(vector<int> v) {
      reverse(v.begin(),v.end());
      return v;
    }
    
    int main() {
      vector<int> x = {1,2,3,5,8,13};
      x = umdrehen(move(x));
    }
    

    Hier wird nirgendswo etwas unnötig kopiert. Der Vektor zieht in den Scope der Funktion um, wird dort verändert und zieht dann quasi wieder zurück. So erlaubt C++2011 einen etwas funktionaleren Stil, ohne dass man sich mit Zeigern und Referenzen die Hände dreckig machen muss und ohne hohe Kosten bzgl Performanz.
    ...
    kk

    Ok super, jetzt hab ichs glaub grob verstanden. Ich wollte garnicht darauf raus, dass eine Kopie verändert werden kann und bei einem Zeiger oder einer Referenz die Variable außerhalb auch geändert wird.

    Bei dem C++11 Beispiel erkennt der Compiler also automatisch, ob der übergebene Vektor sich selbst wieder zugewiesen bekommt?!
    Bei vorhergehenden Versionen hätte er hier also eine Kopie auf den Stack gelegt und eine Kopie zurückgegeben?



  • krümelkacker schrieb:

    ...

    http://ideone.com/WYEql


  • Mod

    314159265358979 schrieb:

    krümelkacker schrieb:

    ...

    http://ideone.com/WYEql

    Möchtest du uns etwas mitteilen?

    language: C++0x (gcc-4.5.1)



  • Wie kommst du nur darauf.



  • Alles, was ich da sehe, ist, dass der GCC 4.5.1 nicht C++11-konform ist.
    Gibt's da sonst noch was zu sehen?



  • Hi,
    auch heute wieder, gibt es Senf 🙂

    Bei der Übergabe von Strukturen und Klassen, oder besser Objekten,
    ist es immer besser eine (konstante) Referenz zu nehmen.

    Wenn man einen Zeiger nimmt, muss man zuviel mit dem * Arbeiten.
    Wenn man eine Kopie des Objektes erzeugt, wird erst der CopyCTor aufgerufen und
    am ende der Funktion wieder der entsprechende DTor. Wenn jetzt der Vector
    auch noch als Elementtyp ein Objekt hat kann es je nach implementation
    quasi eeewig dauern bis Du vollständig deine Funktion betreten hast.

    Seid gegrüßt



  • Albert12 schrieb:

    Bei dem C++11 Beispiel erkennt der Compiler also automatisch, ob der übergebene Vektor sich selbst wieder zugewiesen bekommt?!

    Ob er das "erkennt" oder nicht, spielt gar keine Rolle. Die Funktion liefert einen namenlosen Vektor zurück und der entsprechende Zuweisungsoperator nutzt dies aus und klaut dem temporären Vektor quasi seine Eingeweide.

    Albert12 schrieb:

    Bei vorhergehenden Versionen hätte er hier also eine Kopie auf den Stack gelegt und eine Kopie zurückgegeben?

    Das passiert immer noch. Aus der überflüssigen Kopie wird nur ein „Move“.



  • krümelkacker schrieb:

    Alles, was ich da sehe, ist, dass der GCC 4.5.1 nicht C++11-konform ist.
    Gibt's da sonst noch was zu sehen?

    Letztes mal hat man mir gesagt, bei Argumenten ist das nicht erlaubt. Dieses mal sagt man mir der GCC wäre nicht standardkonform.
    Verarschen kann ich mich selbst.



  • Wahrscheinlich verwechselst du das gerade mit NRVO, was – soweit ich weiß – in dem Fall nicht mehr erlaubt ist. Nicht, dass das schlimm wäre; denn das hat ja eh kein Compiler „geschafft“.

    Der GCC 4.6.1 macht das jedenfalls richtig.


  • Mod

    krümelkacker schrieb:

    Wahrscheinlich verwechselst du das gerade mit NRVO, was – soweit ich weiß – in dem Fall nicht mehr erlaubt ist. Nicht, dass das schlimm wäre; denn das hat ja eh kein Compiler „geschafft“.

    Der GCC 4.6.1 macht das jedenfalls richtig.

    Danke für die Erläuterung, krümelkacker... jetzt ist mir klar, was pi meinte.

    @pi: Ein Link auf ideone stellt kein Argument dar; das musst du schon noch dazu formulieren, wenn du verstanden werden willst.


Anmelden zum Antworten