[C++] Objekte zurückgeben (Heap, Stack)

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Psycho
Establishment
Beiträge: 156
Registriert: 16.09.2002, 14:23

[C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Psycho »

Hallo,

welche Variante bevorzugt ihr für Funktionen, die Objekte konstruieren und zurückgeben?
Bitte unter der Annahme, dass Copy-Elison nicht garantiert ist.

Gerne auch differenzierte Antworten ala “new, solange MyClass nicht größer als 1 KB ist.“

Code: Alles auswählen

MyClass variante1()
{
  return MyClass();
}

smart_pointer<MyClass> variante2()
{
  return new MyClass();
}
smart_pointer kann auto_ptr, shared_ptr, unique_ptr etc. sein.

void variante3(MyClass &) kommt nicht in Frage,
weil MyClass' Konstruktor vielleicht Parameter braucht, die erst in der
Funktion bekannt sind.

std::move finde ich an der Stelle schlecht,
denn das möchte ich nicht jedesmal hinschreiben
(und das nicht nur aus Faulheit).
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von dot »

Lässt sich allgemein nicht beantworten, hängt davon ab, mit was für Objekten man es konkret zu tun hat.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von CodingCat »

Psycho hat geschrieben:std::move finde ich an der Stelle schlecht,
denn das möchte ich nicht jedesmal hinschreiben
(und das nicht nur aus Faulheit).
An welcher Stelle? move müsstest du allenfalls anstatt new hinschreiben, ich sehe da absolut kein Problem. Tatsächlich solltest du auf move aber sogar ganz verzichten, siehe unten.
Psycho hat geschrieben:Bitte unter der Annahme, dass Copy-Elison nicht garantiert ist.
Copy Elision ist zwar nicht garantiert, aber so zuverlässig und effektiv, dass ich sie auf keinen Fall außen vor lassen würde.

Wie dot sagt, hängt die Wahl sehr vom Kontext ab. Ich nutze den Freispeicher eigentlich nur zur einmaligen Erzeugung von Objekten, die eine Identität haben (also von anderen über eine Adresse referenziert werden). Haben solche Objekte Zustand, so werden diese im Anschluss meist mit Variante 3 (Ausgabeparameter oder implizit über this) manipuliert. Manchmal hast du auch keine Wahl, nämlich immer dann, wenn du Interface-Zeiger auf polymorphe Objekte zurückgeben musst. In diesem Fall kommst du um den Freispeicher gar nicht herum.

Davon abgesehen verlasse ich mich bei Objekten, deren Kopieraufwand (mit wie ohne move) nicht unverhältnismäßig hoch wäre, praktisch immer auf implizites move und Copy Elision. Wenn du in einer beliebigen Funktion ein zurückzugebendes Objekt als allererstes anlegst, kannst du dir praktisch sicher sein, dass bei der Rückgabe keine Kopie stattfinden wird. Ist das mal nicht möglich, und Copy Elision dadurch für den Compiler keine Option, muss er stattdessen (vom Standard garantiert) bei der Rückgabe automatisch auf implizites move umsteigen.

Auf Aufruferseite wird so wie so immer automatisch entweder Copy Elision (bei Initialisierung) oder ein implizites move (Move Assignment) stattfinden, egal was du in der Funktion tust. Im Übrigen ist es deshalb ratsam, Funktionsergebnisse immer direkt in einer Initialisierung entgegenzunehmen.

Hier ein Testlauf mit gcc, MSVC++10 liefert dasselbe: http://ideone.com/KVnyF

Man beachte, dass ein manuelles move bei der Rückgabe Copy Elision verhindert. Das heißt konkret, dass du bei der Rückgabe von Objekten weder im return-Statement in der Funktion noch an der Stelle des Aufrufs außerhalb der Funktion jemals manuell move aufrufen solltest. Es wird garantiert, dass automatisch alles so optimal wie möglich läuft, und bei Verfügbarkeit von Move Semantics garantiert nie eine Kopie stattfindet.

Nachtrag zu den Zeigern: unique_ptr ist bei Rückgabe über den Freispeicher wohl immer die richtige Wahl, wenn du nicht von vorneherein weißt, dass geteilte Besitzverhältnisse zu erwarten sind.

Nachtrag 2: Manuelles move unterdrückt Copy Elision, Beitrag entsprechend angepasst.

Nachtrag 3: Komplettierter Testlauf: http://ideone.com/KVnyF

Nachtrag 4: Implizites move auf Aufruferseite ist übrigens vom Standard garantiert, sollte das nicht klar sein. Zu implizitem move im return-Statement ohne Copy Elision finde ich leider gerade nichts.

Nachtrag 5: Auch im return-Statement wird an Stellen, an denen Copy Elision erlaubt ist, implizites move garantiert. Beitrag entsprechend aktualisiert.
Zuletzt geändert von CodingCat am 15.06.2012, 18:59, insgesamt 2-mal geändert.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von eXile »

CodingCat hat geschrieben:Nachtrag zu den Zeigern: unique_ptr ist bei Rückgabe über den Freispeicher wohl immer die richtige Wahl, wenn du nicht von vorneherein weißt, dass geteilte Besitzverhältnisse zu erwarten sind.
Nachdem in nun einige Zeit mit unique_ptr, shared_ptr und weak_ptr gearbeitet habe, bin ich nicht ganz dieser Meinung, sondern: unique_ptr ist immer die richtige Wahl, wenn man von vornherein ausschließen kann, dass geteilte Besitzverhältnisse auftreten können. Nur zu oft musste ich „mal schnell“ einen Verweis anlegen, weil eine andere Objektinstanz diese Instanz referenzieren musste; geht man da dann den Weg über unique_ptr steht man dann mit einem nakten Pointer da, welcher einem beliebig wieder weggezogen werden kann, wenn man irgendwelche Seiteneffekte auslöst. :)

(Ja, normale Pointer habe ihre Daseinsberechtigung; das streite ich auch nicht ab. Nur ich sehe starke Parallelen zum Singleton-Pattern, als das: Es trifft eben häufig nicht zu, dass nur ein Objekt eine alleinige Referenz hält. ;))
CodingCat hat geschrieben:Manchmal hast du auch keine Wahl, nämlich immer dann, wenn du Interface-Zeiger auf polymorphe Objekte zurückgeben musst. In diesem Fall kommst du um den Freispeicher gar nicht herum.
Vermutlich verstehe ich dich gerade falsch; aber als solches sollte das doch eigentlich gehen.
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Schrompf »

Wenn ich das richtig verstehe, müsste Polymorphie auch mit Referenzen prima klappen.

Code: Alles auswählen

class Base { 
virtual void print() { std::cout << "base" << std::endl; }
};

class Derived : public Base {
  void print() override { std::cout << "derived" << std::endl; }
};

Base& GibMir() { static Derived bla; return bla; }

int main( int argc, char** argv)
{
  GibMir().print();
}
kann ich leider auf die Schnelle grad nicht ausprobieren, weil dieser interessant ausschauende Dienst, den Exile da ausgegraben hat, immernoch bei "Loading" steht.

[edit] Einmal "Refresh" auf der Seite und man bekommt das Ergebnis. Und ja, es klappt: virtuelle Methoden werden auch mit Referenzen korrekt aufgelöst. Exiles Beispiel umgeschrieben: http://ideone.com/Edudx
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von CodingCat »

Klar geht Polymorphie mit Referenzen und klar kannst du Interface-Zeiger oder Referenzen auf bestehende polymorphe Objekte zurückgeben. Da es hier aber um die Rückgabe von Objekten geht, hilft das nicht wirklich weiter, es sei denn, man legt Ergebnisse in alter Nebenwirkungsmanier in statischen Variablen ab und gibt dabei sämtliche gute Softwarepraxis auf. ;)
eXile hat geschrieben:
CodingCat hat geschrieben:Nachtrag zu den Zeigern: unique_ptr ist bei Rückgabe über den Freispeicher wohl immer die richtige Wahl, wenn du nicht von vorneherein weißt, dass geteilte Besitzverhältnisse zu erwarten sind.
Nachdem in nun einige Zeit mit unique_ptr, shared_ptr und weak_ptr gearbeitet habe, bin ich nicht ganz dieser Meinung, sondern: unique_ptr ist immer die richtige Wahl, wenn man von vornherein ausschließen kann, dass geteilte Besitzverhältnisse auftreten können. Nur zu oft musste ich „mal schnell“ einen Verweis anlegen, weil eine andere Objektinstanz diese Instanz referenzieren musste; geht man da dann den Weg über unique_ptr steht man dann mit einem nakten Pointer da, welcher einem beliebig wieder weggezogen werden kann, wenn man irgendwelche Seiteneffekte auslöst. :)
Naja, hier kommen wir wieder in die GC/Ownership-Diskussion. Ich habe das lange Zeit auch so gehalten, aber inzwischen ärgere ich mich, dass ich so viel geteilten Besitz in meinem Programm habe. Davon abgesehen lässt sich ein unique_ptr doch jederzeit in einen shared_ptr umwandeln?! Alles, was du dadurch verlierst, ist die make_shared-Optimierung. Wenn aber die make_shared-Optimierung wichtig ist, dann dürfte in der Regel ohnehin von vorneherein klar sein, dass das entsprechende Objekt auf geteilten Besitz ausgelegt ist.
eXile hat geschrieben:(Ja, normale Pointer habe ihre Daseinsberechtigung; das streite ich auch nicht ab. Nur ich sehe starke Parallelen zum Singleton-Pattern, als das: Es trifft eben häufig nicht zu, dass nur ein Objekt eine alleinige Referenz hält. ;))
Ich sehe da ganz andere Parallelen, nämlich die, dass die Lebensdauer von geteilten Objekten häufig doch sehr genau durch die Abhängigkeiten bestimmt ist, shared_ptr jedoch nur genutzt wird, weil sich der Programmierer dieser Abhängigkeiten nicht bewusst ist. Geteilte Besitzverhältnisse verschleiern immer auch Abhängigkeiten, was wiederum zur Verschleierung von Fehlern führen kann. (Arbeiten auf längst aufgegebenen Objekten, out-of-order Destruction, Leaks ...)

Im Übrigen sollte nie vergessen werden, dass unique_ptr kein Objekt auf ewig an sich bindet. Genau wie bei shared_ptr kann der Besitzer nach Belieben wechseln, solange es nur einen Besitzer gibt. Damit sollten für shared_ptr nur sehr wenige Fälle übrig bleiben. Die Übernahme von Besitz drückt man durch die Entgegennahme eines unique_ptr by Value aus:

Code: Alles auswählen

void take_foo_ownership(unique_ptr<foo> obj)
{
   // Ergreife Besitz
   my_foo = move(obj);
}

// Legale Aufrufe
take_foo_ownership(move(someFoo));
take_foo_ownership(unique_ptr<foo>(new foo));
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Psycho
Establishment
Beiträge: 156
Registriert: 16.09.2002, 14:23

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Psycho »

Danke für Eure Antworten und die guten Beispiele.

Sehr bedauerlich, dass es wohl keine simple Möglichkeit gibt, die Implementierung eines Interfaces direkt über den Stack zurückzugeben.
Wrapper-Klassen und statics finde ich dafür, diplomatisch ausgedrückt, abscheulich.
Klassen wie StaticArray<10000> werde ich wohl auf dem Heap parken, Klassen wie TcpConnection kann ich wohl gar nicht auf dem Stack zurückgeben, weil sich so ein Objekt ja gar nicht kopieren lässt (oder es macht eine zweite Verbindung auf). Mag sein, dass es auf den meisten Compilern durch Copy-Elision nicht passieren würde, aber wenn ich mich nicht darauf verlassen kann, möchte ich es nicht so einsetzen.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von dot »

Psycho hat geschrieben:Sehr bedauerlich, dass es wohl keine simple Möglichkeit gibt, die Implementierung eines Interfaces direkt über den Stack zurückzugeben.
Gibt es doch!? Einfach eben eine Kopie des Objekts zurückgeben...
Psycho
Establishment
Beiträge: 156
Registriert: 16.09.2002, 14:23

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Psycho »

Sorry, war schlecht ausgedrückt. Ich meine sowas:

Code: Alles auswählen

#include <iostream>

class MyInterface
{
  public: virtual void foo() = 0;
};

class MyClass : public MyInterface
{
  public: void foo() { std::cout << "foo!"; }
};

MyInterface myFunction()
{
  return MyClass();
}
Zuletzt geändert von Psycho am 15.06.2012, 15:35, insgesamt 1-mal geändert.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von dot »

Sowas macht schon rein logisch betrachtet keinen Sinn, denn ein Interface ist rein konzeptuell kein Objekt.
Alexander Kornrumpf
Moderator
Beiträge: 2138
Registriert: 25.02.2009, 13:37

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Alexander Kornrumpf »

Mal ne ganz dumme Frage zu diesen neuen Pointer Typen. Wenn ein Objekt genau einem anderen gehört, aber potentiell viele andere kennen es und operieren damit. Wie wird das abgebildet? unique_ptr klingt wie "es gibt nur diesen einen einzigen Pointer auf das Objekt". Das klingt nicht wie etwas das oft vorkommt.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von dot »

Du musst unterscheiden zwischen Ownership und einfach nur einer Referenz. unique_ptr verwendest du dort wo das Objekt lebt. Andere Objekte, die das Objekt nur kennen und damit operieren müssen, bekommen einfach eine stinknormale Referenz bzw. einen stinknormalen Pointer drauf. Es gibt beliebig viele Pointer und Referenzen auf das Objekt. Aber es gibt nur einem Besitzer. Meiner Erfahrung nach ist genau das der Regelfall und Shared Ownership ein sehr seltener Ausnahmefall. (kann mich ehrlich gesagt gar nicht erinnern wann ich sowas tatsächlich jemals gebraucht hätte)
Alexander Kornrumpf
Moderator
Beiträge: 2138
Registriert: 25.02.2009, 13:37

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Alexander Kornrumpf »

Ah, ich hab immer gedacht, wenn man diese typen verwendet würde man nicht niemals nie mehr normale pointer verwenden.
Psycho
Establishment
Beiträge: 156
Registriert: 16.09.2002, 14:23

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Psycho »

Ich könnte mir vorstellen, unique_ptr so zu verwenden:

Code: Alles auswählen

unique_ptr<MyInterface> myFunction()
{
  return new MyClass();
}

void workWithIt(MyInterface &myInterface)
{
  // [...]
}

void foo()
{
  unique_ptr<MyInterface> myInterface = myFunction();
  workWithIt(*myInterface);
}
---
Oh, etwas zu spät.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von dot »

Psycho hat geschrieben:Ich könnte mir vorstellen, unique_ptr so zu verwenden: [...]
Exakt, wobei ich versuchen würde, unique_ptr auf Interfaces eher zu vermeiden. Ich würde stattdessen wohl einfach einen unique_ptr auf das jeweilige Objekt zurückgeben. Meistens ist es so, dass dort, wo das Objekt erzeugt wird, auch sein Typ bekannt ist. Ein Interface geht dann an die Funktionen/Methoden/Objekte, die damit arbeiten...

Ein Interface sieht bei mir in C++ normalerweise so aus (__declspec(novtable) für MSVC, falls jemand weiß, wie man was Vergleichbares unter anderen Compilern erreichen kann, wäre ich sehr interessiert):

Code: Alles auswählen

class __declspec(novtable) AwesomeInterface
{
protected:
  AwesomeInterface() {}
  AwesomeInterface(const AwesomeInterface&) {}
  AwesomeInterface& operator =(const AwesomeInterface&) { return *this; }
  ~AwesomeInterface() {}
public:
  virtual void blub() = 0;
};
Und implementiert wird es, indem (public) virtual davon abgeleitet wird. Leider einiges an Boilerplate Code, aber dafür sehr mächtig. Man kann so auch richtige Werttypen Interfaces implementieren lassen. Was ich in anderen Sprachen auch oft vermisse, ist die Möglichkeit Interfaces private zu implementieren.
Zuletzt geändert von dot am 15.06.2012, 16:26, insgesamt 3-mal geändert.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von CodingCat »

Psycho hat geschrieben:Klassen wie TcpConnection kann ich wohl gar nicht auf dem Stack zurückgeben, weil sich so ein Objekt ja gar nicht kopieren lässt (oder es macht eine zweite Verbindung auf). Mag sein, dass es auf den meisten Compilern durch Copy-Elision nicht passieren würde, aber wenn ich mich nicht darauf verlassen kann, möchte ich es nicht so einsetzen.
Ja und nein. ;) Ja, auch ich würde TcpConnection-Objekte vermutlich in die Kategorie von Objekten mit Identität einordnen, und aus purer Bequemlichkeit Kopie und Zuweisung gänzlich verbieten. Tatsächlich bietet dir C++11 mit Move Semantics aber erstmals die Möglichkeit, auch in diesem Fall elegant mit Objekten an Stelle von Adressen auf dieselben zu arbeiten, ohne dass dabei Wrapper-Klassen zum Einsatz kommen. Denn hinter Move Semantics steht letztendlich genau die Idee, dass jede Klasse eigentlich von sich aus bereits Wrapper irgendeiner bestimmten Ressource oder Funktionalität ist. Die vollständige C++11-Implementierung einer TcpConnection würde also Kopie und Zuweisung verbieten, aber dafür Move-Konstruktion (und optional Move-Zuweisung) implementieren. Der korrekte Weg, eine solche TcpConnection zurückzugeben, ohne dabei auf den Freispeicher zuzugreifen, wäre dann:

Code: Alles auswählen

TcpConnection createConnection(...)
{
   ...
   TcpConnection connection(...);
   ...
   return connection; // Standard garantiert Überladungsauflösung mit connection als rvalue (d.h. implizites move) oder Copy Elision
}
// oder
TcpConnection createConnection(...)
{
   return TcpConnection(...); // Temporäre Variable, also move oder Copy Elision
}

TcpConnection con = TcpConnection(...); // Copy Elision oder Move Construction
con = TcpConnection(...); // Move Assignment
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von CodingCat »

So, ich habe die Stelle im Standard gefunden, die implizites move an allen Stellen garantiert, an denen Copy Elision erlaubt ist, aber durch den Compiler aus welchem Grund auch immer nicht umgesetzt wird.
C++11 §12.8 hat geschrieben:When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue
Ich habe meine Beiträge in diesem Thread entsprechend aktualisiert, hier nochmal die Kurzfassung:

Wenn die Klasse eines zurückgegebenen Objektes einen Move-Konstruktor anbietet, garantiert der Standard, dass in einem return-Statement der Form return object; der Move-Konstruktor aufgerufen wird, sofern keine Copy Elision angewandt wird (noch effizienter). Damit ist es nutzlos, return std::move(object); zu schreiben, schlimmer noch, man schaltet damit Copy Elision ab.

Wird auf Aufruferseite ein zurückgegebenes Objekt in einer Initialisierung entgegengenommen, garantiert der Standard ebenfalls entweder Copy Elision oder Move-Konstruktion, sofern die Klasse des Zielobjekts einen entsprechenden Move-Konstruktor implementiert. Wird auf Aufruferseite ein zurückgegebenes Objekt in einer Zuweisung entgegengenommen, garantiert der Standard Move-Zuweisung, sofern die Klasse des Zielobjekts Objekts einen entsprechenden Move-Assignment-Operator implementiert.

Fazit: Objekte können und sollten in C++11 ohne zusätzliche Aufrufe von move ganz intuitiv by Value zurückgegeben werden, um optimales Verhalten sicherzustellen. Copy Elision wird zwar nicht vom Standard garantiert, Movement dafür schon. Die naivste und intuitivste Form ist hier somit auch die beste, wie es sich für eine gute Sprache gehört. ;)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Psycho
Establishment
Beiträge: 156
Registriert: 16.09.2002, 14:23

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Psycho »

Das ist doch eine gute Nachricht, vielen Dank für die Recherche.
Ubseht
Beiträge: 6
Registriert: 01.06.2012, 17:59

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Ubseht »

CodingCat hat geschrieben:
Fazit: Objekte können und sollten in C++11 ohne zusätzliche Aufrufe von move ganz intuitiv by Value zurückgegeben werden, um optimales Verhalten sicherzustellen. Copy Elision wird zwar nicht vom Standard garantiert, Movement dafür schon. Die naivste und intuitivste Form ist hier somit auch die beste, wie es sich für eine gute Sprache gehört. ;)
Naja, praktisch und sehr sinnvoll ist dieses Verhalten natürlich schon. Allerdings ist es ja nur ein Spezialfall und die Sprache wird dadurch wieder ein Stück komplizierter. Erwarten würde man schließlich, wenn man den Standard nicht sehr genau kennt, dass hier der Copy-Constructor benutzt wird. Davon ausgehend würde man möglicherweise versuchen, das Kopieren durch eine Umstrukturierung des Programmes (z.B. Objekte auf Heap + Pointer) zu verhindern, obwohl dies ja nach Standard gar nicht notwendig ist. Diese Kritik gilt natürlich auch schon für den Einsatz von Copy Elision bei früheren Compilern.

Um solche Fallen zu vermeiden, braucht es wohl den Einsatz von Tools wie Profilern oder zumindest Tracing. Optimierungen sollten dann auch nur, anhand der dadurch erzielten Ergebnisse, durchgeführt werden. Alternativ wäre wohl nur ein C++11-Standard Studium nach Koranschülerart.

Trotzdem halte ich diese Standardvorgabe persönlich für eine gute Sache, da, wie du schon schriebst, so einfacher, eleganter Code in Kombination mit einer effizienten Übersetzung ermöglicht wird. Problem ist nur, dass man es wissen muss. D.h. dies ist ein Punkt der in den meisten Werken zu C++11 unbedingt auftauchen sollte.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von dot »

Ubseht hat geschrieben:Erwarten würde man schließlich, wenn man den Standard nicht sehr genau kennt, dass hier der Copy-Constructor benutzt wird.
Nicht unbedingt: Da es sich um ein lokales Objekt handelt, wird dieses unmittelbar nach dem return Statement auf jeden Fall zerstört. Es wäre also imo eigentlich naheliegend, anzunehmen, dass hier, wenn möglich, gemoved wird...
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von CodingCat »

Ubseht hat geschrieben:
CodingCat hat geschrieben:Fazit: Objekte können und sollten in C++11 ohne zusätzliche Aufrufe von move ganz intuitiv by Value zurückgegeben werden, um optimales Verhalten sicherzustellen. Copy Elision wird zwar nicht vom Standard garantiert, Movement dafür schon. Die naivste und intuitivste Form ist hier somit auch die beste, wie es sich für eine gute Sprache gehört. ;)
Naja, praktisch und sehr sinnvoll ist dieses Verhalten natürlich schon. Allerdings ist es ja nur ein Spezialfall und die Sprache wird dadurch wieder ein Stück komplizierter. Erwarten würde man schließlich, wenn man den Standard nicht sehr genau kennt, dass hier der Copy-Constructor benutzt wird. Davon ausgehend würde man möglicherweise versuchen, das Kopieren durch eine Umstrukturierung des Programmes (z.B. Objekte auf Heap + Pointer) zu verhindern, obwohl dies ja nach Standard gar nicht notwendig ist. Diese Kritik gilt natürlich auch schon für den Einsatz von Copy Elision bei früheren Compilern.
Erwartetes Verhalten ist doch, dass das Objekt zurückgegeben wird. Im Optimalfall würde einfach immer Copy Elision durchgeführt und C++ wäre ein Traum. Das, was wir vor C++11 schmerzhaft lernen mussten, war doch gerade, dass es keinen Weg der Rückgabe von Objekten gab, und wir stattdessen auf den Freispeicher ausweichen mussten, was nicht nur zu vollkommen unnötiger Inneffizienz, sondern auch zu einer ganzen Palette von zusätzlichen Fehlerquellen führte. Diese Einsicht erforderte zudem die Auseinandersetzung mit dem ggf. doppelten(!) Kopierverhalten bei der Rückgabe und damit unzweifelhaft tiefere Sprachkenntnis (manuelle Speicherverwaltung nur zur Vermeidung von Kopierlogik, das ist doch alles andere als intuitiv?!). Mit C++11 können wir endlich genau das hinschreiben, was wir wollen: Die Rückgabe eines Objekts, ohne dass wir uns dabei um Speicherverwaltung oder um Kopierverhalten kümmern müssen.

Mit Move Semantics ist die Komplexität in der Tat leicht angewachsen, aber keinesfalls wegen eines Sonderfalls. Tatsächlich ist das doch mal wieder ein geniales Gesamtkonzept, das maximale Effizienz in allen Bereichen mit sich bringt: Ob bei der Übergabe von Argumenten, bei der Verwaltung von Objekten in Containers, bei der Rückgabe von Objekten oder bei der klaren Formulierung von Besitzverhältnissen; überall lösen die gleichen neuen R-Value-Mechanismen automatisch die entscheidenden Probleme in C++03. (Gerade die klaren Besitzverhältnisse wirken sich wieder positiv auf die sichere Rückgabe von Objekten über den Freispeicher aus, wie sie bei polymorphen Objekten nach wie vor erforderlich ist, siehe Threadanfang.)
Ubseht hat geschrieben:Um solche Fallen zu vermeiden, braucht es wohl den Einsatz von Tools wie Profilern oder zumindest Tracing. Optimierungen sollten dann auch nur, anhand der dadurch erzielten Ergebnisse, durchgeführt werden. Alternativ wäre wohl nur ein C++11-Standard Studium nach Koranschülerart.
Es geht hier um absolut grundlegende Mechanismen und Garantien (also keine unberechenbare Optimierungen!), wie sie hoffentlich in wenigen Jahren in jedem C++-Lehrbuch zu finden sind. Und du willst mir nicht ernsthaft erzählen, dass eine Sprache ineffizientes Verhalten vorschreiben sollte, nur damit das Programm nicht versehentlich zu schnell läuft?! Dass die Sprache jetzt von sich aus maximal effizientes Verhalten vorschreibt, ohne dass ich mich in irgendeiner Weise darum kümmere, verringert doch nur stark das erforderliche Profiling?!
Ubseht hat geschrieben:Trotzdem halte ich diese Standardvorgabe persönlich für eine gute Sache, da, wie du schon schriebst, so einfacher, eleganter Code in Kombination mit einer effizienten Übersetzung ermöglicht wird. Problem ist nur, dass man es wissen muss. D.h. dies ist ein Punkt der in den meisten Werken zu C++11 unbedingt auftauchen sollte.
Ja, genau. Aber selbst wenn der Punkt darin nicht auftaucht, werden die Programme von alleine wesentlich effizienter. Ich sehe wirklich nicht dein Problem. ;)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von CodingCat »

Im Übrigen sollte man das Thema nicht nur auf Effizienz reduzieren, was wir bis hierhin leider relativ konsequent getan haben. Die Garantien wirken sich vor allem sehr positiv auf die Programmstruktur und damit auch das Programmverhalten aus. Objekte auf dem Freispeicher stellen eine enorme Fehlerquelle da: Der Speicher kann leaken oder vorzeitig freigegeben werden; Zeiger können uninitialisiert oder ungültig sein. Schon die Kopie von Objekten, die Unterobjekte im Freispeicher besitzen, ist ein Graus. Einfach zusammengesetzte Objekte hingegen lassen sich vollkommen ohne Zusatzcode ganz sicher korrekt über den impliziten Kopierkonstruktor kopieren.

Mit Movement Semantics wird der Freispeicher endlich zu dem, was er schon immer sein sollte: Eine Ablage von Objekten mit unbestimmter Lebensdauer, nicht eine Ablage von Objekten mit unangenehmen Eigenschaften. Wenn wir Objekte im Freispeicher ablegen, dann wollen wir den Zeitpunkt ihrer Zerstörung/Freigabe selbst bestimmen. Ist der Freispeicher dagegen, wie vor C++11 leider allzu häufig, eine Halde für alle Arten von unangenehmen Objekten, dann müssen wir uns an allerlei Stellen nebenbei um die Lebensdauer kümmern, und vergessen es ggf.

Movement Semantics erlauben es uns auch, Kopien vollends zu verbieten, ohne Objekte unbenutzbar (= nur für den Freispeicher bestimmt) zu machen. Kopierkonstruktoren und/oder Zuweisungsoperatoren sind für komplexe Objekte unglaublich mühsam zu schreiben und damit auch fehleranfällig. Move-Konstruktoren dagegen sind meist absolut simpel und beschränken sich auf wenige Zuweisungen von Zeigern/Werten. Wenn wir in solchen Objekten also Kopierlogik entfernen (C++11: = delete, zuvor private-Deklaration) und stattdessen Move Semantics implementieren, verringern wir mit den neuen Features sogar die Komplexität und damit Fehleranfälligkeit des Programms.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von dot »

CodingCat hat geschrieben:Mit Movement Semantics wird der Freispeicher endlich zu dem, was er schon immer sein sollte: Eine Ablage von Objekten mit unbestimmter Lebensdauer, nicht eine Ablage von Objekten mit unangenehmen Eigenschaften.
Bild
Ubseht
Beiträge: 6
Registriert: 01.06.2012, 17:59

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von Ubseht »

CodingCat hat geschrieben:
Ubseht hat geschrieben:Trotzdem halte ich diese Standardvorgabe persönlich für eine gute Sache, da, wie du schon schriebst, so einfacher, eleganter Code in Kombination mit einer effizienten Übersetzung ermöglicht wird. Problem ist nur, dass man es wissen muss. D.h. dies ist ein Punkt der in den meisten Werken zu C++11 unbedingt auftauchen sollte.
Ja, genau. Aber selbst wenn der Punkt darin nicht auftaucht, werden die Programme von alleine wesentlich effizienter. Ich sehe wirklich nicht dein Problem. ;)
Habe damit (dank deiner Erläuterungen) auch kein Problem. Andere jedoch möglicherweise schon. Dies ist ja auch der Grund für diesen Thread. Wie geschrieben muss man wissen, dass der Compiler hier dank C++11-Standard genau das Richtige macht. Alternativ kann man auch naiv ohne große C++/C++11 Kenntnisse herangehen und bekommt automatisch das Richtige. Toll! Probleme haben jedoch die Personen in der Mitte wie z.B. der Threadersteller sowie ich selbst. Wir hätten vor deinen Erläuterungen das Kopieren der Rückgabe z.B. durch Smart-Pointer + New (Siehe Threadanfang) verhindert und dadurch unnötig unseren Code verkompliziert sowie fehleranfälliger (deine 2. Antwort auf meinen Beitrag) gemacht.

Das unschöne ist, dass durch die Syntax nicht klar ist, dass hier das Richige getan wird. Im Gegenteil könnte man mit älterem C++-Wissen zurecht (oder täusche ich mich da?) davon ausgehen, dass hier 2x oder 1x kopiert werden könnte.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [C++] Objekte zurückgeben (Heap, Stack)

Beitrag von CodingCat »

Ubseht hat geschrieben:Wie geschrieben muss man wissen, dass der Compiler hier dank C++11-Standard genau das Richtige macht. Alternativ kann man auch naiv ohne große C++/C++11 Kenntnisse herangehen und bekommt automatisch das Richtige. Toll! Probleme haben jedoch die Personen in der Mitte wie z.B. der Threadersteller sowie ich selbst. Wir hätten vor deinen Erläuterungen das Kopieren der Rückgabe z.B. durch Smart-Pointer + New (Siehe Threadanfang) verhindert und dadurch unnötig unseren Code verkompliziert sowie fehleranfälliger (deine 2. Antwort auf meinen Beitrag) gemacht.

Das unschöne ist, dass durch die Syntax nicht klar ist, dass hier das Richige getan wird. Im Gegenteil könnte man mit älterem C++-Wissen zurecht (oder täusche ich mich da?) davon ausgehen, dass hier 2x oder 1x kopiert werden könnte.
Seit C++11 ist durch die Syntax klar, dass hier das Richtige getan wird. Optimierungen sind meist von begrenzter Gültigkeitsdauer, und das ist gut so. Genau das zeigt, dass sich die Technik (hier: die Sprache) in die richtige Richtung entwickelt.

Und ja, in C++03 würden hier zwischen 0 und 2 Kopien durchgeführt. Sogar mit einem C++11-Compiler würden hier mit reinem C++03-Quelltext auch weiterhin zwischen 0 und 2 Kopien durchgeführt. C++11 ist eine neue, wesentlich verbesserte Sprache, und erfordert somit unweigerlich ein neues Erlernen grundlegender Konzepte und Eigenschaften. Du hast dich für C++03 informiert, wie du Rückgabe im jeweiligen Kontext optimal gestalten kannst. Dieses Wissen gilt auch weiterhin für C++03. Das Erstaunliche an C++11 ist, dass selbst mit einem C++11-Compiler dein Wissen Gültigkeit behält. All dein bisheriger Code wird mit einem C++11-Compiler nach wie vor genauso effizient oder effizienter laufen. Der einzige Unterschied ist, dass es jetzt noch eine effizientere Variante gibt. Dass diese Variante zufällig auch noch einfacher und weniger fehleranfällig ist, ist eine glückliche Fügung.

Optimierungswissen ist immer von begrenzter Dauer, und immer mit der Gefahr verbunden, durch die Anpassung an eine Zielplattform andere (auch zukünftige!) zu benachteiligen. Wer Optimierungswissen besitzt und anwendet, ist in jedem Bereich dafür verantwortlich, dieses permanent auf Gültigkeit zu überprüfen und ggf. zu aktualisieren. Dass das Ausbauen komplizierter und fehleranfälliger Mechanismen das Programmverhalten verbessert, wird dabei stets die erfreulichste und zukunftssicherste Form der Aktualisierung bleiben.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Antworten