Kann der Compiler das optimieren?
-
xyzet schrieb:
std::list<int> foo(const std::list<int>& l){ ... }...
also nicht eine kopie einer liste zurück geben, ...Ich sehe hier keine Kopie.
-
camper schrieb:
Ich sehe hier keine Kopie.
std::list<int> foo(const std::list<int>& l){ std::list<int> temp; temp.push_back(8); ... return temp; } std::list<int> l; std::list<int> ret=foo(l);//Wie soll hier sonst was in ret rein kommen?
-
Der Returnwert ist eindeutig eine Kopie. Aber nicht vom Eingabe-Wert!!! Es ist eine Kopie von einem in der Funktion irgendwie erzeugten Wert. Was willst du überhaupt erreichen?
Wenn dann wohl eher sowas:// 1. Variante: Returnwert muß der User selber löschen. std::list<int>* foo(const std::list<int>& l); // 2. Variante: mit Smartpointer std::shared_ptr<std::list<int>> foo(const std::list<int>& l); // 3. Variante: Compiler kann den const-return-Wert optimieren! // Hab ich schon mal mit vector im MSVC beobachten können. const std::list<int> foo(const std::list<int>& l);
-
Hi Artchie,
MSVC kann definitiv auch non-const Rückgabewerte optimieren.
-
xyzet schrieb:
camper schrieb:
Ich sehe hier keine Kopie.
std::list<int> foo(const std::list<int>& l){ std::list<int> temp; temp.push_back(8); ... return temp; } std::list<int> l; std::list<int> ret=foo(l);//Wie soll hier sonst was in ret rein kommen?Aha. Jetzt können wir über Kopien sprechen.
return temp;Diese Kopie darf nach 12.8/15 eliminiert werden. Jeder anständige Compiler wird das in diesem Falle tun. Bei einer gängigen Implementation (das ist allerdings keine Bedingung, die der Standard vorschreibt, und es wären Implementationen denkbar, die nicht derartig beschränkt sind) ist diese sog. NRVO prinzipiell immer dann möglich für ein bestimmtes return x, wenn innerhalb des potentiellen Scopes von x kein erreichbares return-statement existiert, dass nicht x zurückgibt. Wenn man vernünftigen Code schreibt, ist diese Bedingung in der Regel problemlos erfüllbar.
std::list<int> ret=foo(l);Diese Kopie darf ebenfalls nach 12.8/15 eliminiert werden (falls diese Konstellation in return-statements auftaucht, spricht man von RVO). Dieser Fall ist erheblich einfacher und wird von praktisch jedem Compiler durchgeführt.
Interessanter ist dagegen folgender Fall
std::list<int> x; x = foo(l);Offensichtlich können wir hier die Zuweisung nicht einfach eliminieren. Andererseits ist es eigentlich unnötig, den kompletten Inhalt von foo(l) zu kopieren, schließlich wird dieses Objekt danach ohnehin unmittelbar zerstört werden. Was wir hier brauchen, ist eine Möglichkeit, dem foo(l)-Objekt den Inhalt gewissermaßen zu stehlen. Eine explizite Möglichkeit haben wir bereits jetzt:
foo(l).swap(x);Diese tut geringfügig mehr als nötig, aber das Hauptproblem ist, dass es schrecklich aussieht. Eine wirklich gute und portable Lösung ist gegenwärtig nicht möglich (siehe auch http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1610.html), im künftigen Standard wird Move-Semantik dagegen eine große Rolle spielen (und hoffentlich hören die unsinnigen Empfehlungen, const T zurückzugeben, dann endlich auf).
-
also ist es doch besser immer diese
void foo(const std::list<int>& l, std::list<int>& ret) { ... }version zu verwenden, weil wir nicht wissen, wie ein anderer unsere funktion aufruft und ob der compiler dann wirklich die unnötigen kopien entfernen kann.
das const beim return ändert daran doch nicht wirklich was, oder?
-
xyzet schrieb:
also ist es doch besser immer diese
void foo(const std::list<int>& l, std::list<int>& ret) { ... }version zu verwenden
Nein, was soll daran besser sein? Wozu sind denn Rückgabewerte da?
Es ist furchtbar, wieviele Verbrechen im Namen der Optimierung begangen werden. Code soll in erster Linie lesbar sein! Rückgabeparameter sind nicht lesbar.
Ich bin da sogar noch radikaler. Ich vertrete die Meinung, dass es nur eine einzige Funktion gibt, in der eine By-Ref-Rückgabe erlaubt sein sollte, nämlich die 'swap'-Funktion. Alles andere sollte gefälligst per Rückgabewert erledigt werden. und 'swap' wird auch nur deswegen von mir geduldet, weil C++ keine effiziente Semantik hat, um die doppelten Rückgabewerte zu verarbeiten.
-
xyzet schrieb:
weil wir nicht wissen, wie ein anderer unsere funktion aufruft
Konzentriere dich beim Schreiben einer Funktion auf die Funktion der Funktion. Was der Aufrufer damit anstellt, ist seine Sache und geht dich als Funktionsschreiber nichts an - Die Signatur der Funktion zu ändern entspricht in etwa einem Pop-up, dass jedesmal auftaucht, wenn ein Aufruf geschrieben werden soll: 'Heh, Aufrufer, ich weiß, dass du ein Idiot bist und habe mir meinen Kopf für dich zerbrochen und alles gaaaaaaaaaaaaaanz einfach gemacht'. Eine normale Funktionsrückgabe favorisiert keine bestimmt Optimierung und verhindert auch keine. Das ist nicht so, wenn konstante Objekte zurückgegeben werden oder Referenzargumente benutzt werden.
-
Kann hier Konrad und Camper voll zustimmen. Rückgabe-Parameter sind genauso schlecht, wie Leute die aus angeblichen Performance-Gründen in C++ C-Code schreiben und z.B. die Std-Lib meiden. Das ist nicht der Sinn Sprachmittel (wie den Returnwert) einzuführen, und sie dann zu meiden.
-
Habe trotzdem noch ne Frage:
class A { string s; public: string get_s() { return s; } };Hier wird ja keine temporäre Variable zurück gegeben. Gelten die gleichen Regeln wie oben?
-
s ist weder ein temporäres Objekt noch eine automatische Variable - der Aufruf des Copy-Konstruktors ist daher zwingend.
cout << (foo.get_s()+="bar");
-
woher weiß der compiler, dass er diese nrvo machen darf, wenn er die aufgerufene funktion nicht kennt? oder wird einfach dann sozusagen per default die referenz auf die "rückgabe"-instanz auf den stack geschrieben und die funktion kann sich dann aussuchen, ob sie diese referenz verwendet oder nicht? das wäre dann eine sache der abi, sodass diese optimierung voraussetzt, dass der compiler, der den funktionsaufruf erzeugt, auch dieses abi unterstützt.
-
Artchi schrieb:
Rückgabe-Parameter sind genauso schlecht, wie Leute die aus angeblichen Performance-Gründen in C++ C-Code schreiben und z.B. die Std-Lib meiden. Das ist nicht der Sinn Sprachmittel (wie den Returnwert) einzuführen, und sie dann zu meiden.
Hier fühle ich mich nun doch gezwungen zu widersprechen. Was den Rückgabewert angeht, stimme ich für den allgemeinen Fall zu, jedoch halte ich den Vergleich mit der C++-Standard-Library, die in viel zu vielen relevanten Bereichen ihre Schwächen hat, für sehr unangebracht.
C-Dateistreams sind schneller, sprintf ist schneller und kleiner, STL-Container werden schnell zur Bloatware. Meßbare Fakten.Zum Thema Rückgabewerte: allgemein sollte man sie freilich verwenden, aber wenn eine Funktion mehr als einen Wert zurückgibt (das Windows-API ist ja voll solcher Funktionen), ist die Lösung über Referenzparameter die einzig sinnvolle und übersichtliche. Dokumentation ist alles.
Wenn das Kopieren eines Rückgabewertes Potential zum Bottleneck hat, gebe ich einfach stattdessen einen referenzzählenden Smartpointer darauf zurück.
@namenlos: Rückgabewerte größeren Typs werden i.d.R. ohnehin zuerst, wenn nötig, vom Aufrufer auf dem Stack allokiert und dann per Referenzparameter angesprochen, so daß die aufgerufene Funktion eine lokale Instanz des Typs auch gleich an diese Stelle schreiben kann.
-
audacia schrieb:
Artchi schrieb:
Rückgabe-Parameter sind genauso schlecht, wie Leute die aus angeblichen Performance-Gründen in C++ C-Code schreiben und z.B. die Std-Lib meiden. Das ist nicht der Sinn Sprachmittel (wie den Returnwert) einzuführen, und sie dann zu meiden.
Hier fühle ich mich nun doch gezwungen zu widersprechen. Was den Rückgabewert angeht, stimme ich für den allgemeinen Fall zu, jedoch halte ich den Vergleich mit der C++-Standard-Library, die in viel zu vielen relevanten Bereichen ihre Schwächen hat, für sehr unangebracht.
C-Dateistreams sind schneller, sprintf ist schneller und kleiner, STL-Container werden schnell zur Bloatware. Meßbare Fakten.Erwiesener Schwachsinn. Mag für einige Implementierungen zustimmen, aber nicht für alle.
'sprintf' *kann* theoretisch gar nicht so schnell implementiert werden wie ein gut genutzter Stringstream, weil 'sprintf' zur Laufzeit den Formatstring parsen muss. STL-Container als Bloatware kann ich nicht nachvollziehen. Und dass C-Datenstreams überall schneller sind, stimmt so auch nicht. Ich habe schon gegenteilige Messergebnisse gelesen.
Zum Thema Rückgabewerte: allgemein sollte man sie freilich verwenden, aber wenn eine Funktion mehr als einen Wert zurückgibt (das Windows-API ist ja voll solcher Funktionen), ist die Lösung über Referenzparameter die einzig sinnvolle und übersichtliche.
Nein, Quatsch. Wozu gibt es Tupel?
-
Konrad Rudolph schrieb:
Erwiesener Schwachsinn. Mag für einige Implementierungen zustimmen, aber nicht für alle.
Für die meisten trifft es leider zu.
Konrad Rudolph schrieb:
'sprintf' *kann* theoretisch gar nicht so schnell implementiert werden wie ein gut genutzter Stringstream, weil 'sprintf' zur Laufzeit den Formatstring parsen muss.
Theoretisch, ja. In der Praxis ist sprintf meist nicht nur schneller und kleiner, sondern auch viel einfacher für die Lokalisierung. Und dank der Spracherweiterungen von C++ kann man auch typsichere sprintf-Versionen schreiben.
Nein, Quatsch. Wozu gibt es Tupel?
Schauen wir mal:
BOOL ReadFile( HANDLE hFile, // handle of file to read LPVOID lpBuffer, // address of buffer that receives data DWORD nNumberOfBytesToRead, // number of bytes to read LPDWORD lpNumberOfBytesRead, // address of number of bytes read LPOVERLAPPED lpOverlapped // address of structure for data );würde zu
struct READFILE_RESULT { BOOL bSuccess; std::vector <BYTE> vBuffer; OVERLAPPED overlappedOut; } READFILE_RESULT MyReadFile (HANDLE hFile, DWORD nNumberOfBytesToRead, OVERLAPPED overlappedIn);Zweifelsohne wunderschön. Der Compiler wird es schon wegoptimieren können.
Wäre ReadFile freilich in anständigem C++ geschrieben, so hätte man das Problem mit OVERLAPPED freilich einfacher lösen können, indem man es als private Membervariable einer File-Klasse implementiert hätte, so daß es in der Schnittstelle gar nicht auftaucht. Dafür müßte man eben noch die Funktion überladen:
READFILE_RESULT File::ReadFile (DWORD nNumberOfBytesToRead); READFILE_RESULT File::ReadFile (DWORD nNumberOfBytesToRead, DWORD dwOffs); READFILE_RESULT File::ReadFile (DWORD nNumberOfBytesToRead, HANDLE hOverlappedEvent); READFILE_RESULT File::ReadFile (DWORD nNumberOfBytesToRead, DWORD dwOffs, HANDLE hOverlappedEvent);Außerdem paßt uns dann natürlich die Fixierung auf std::vector nicht. Vielleicht will ja jemand einen anderen Container verwenden? Wir implementieren ReadFile also für beliebige Container folgendermaßen:
template <typename T, template <typename> class Cont> struct READFILE_RESULT { BOOL bSuccess; Cont <T> buffer; } template <typename T, template <typename> class Cont> READFILE_RESULT <T, Cont <T> > File::ReadFile (DWORD nNumberOfItemsToRead /* ... */) { // template-Funktionen werden natürlich im Header implementiert. Äußerst praktisch besonders für Windows-eigene Funktionen. }SCNR. Natürlich gibt es viele Fälle, in denen auch Tupel sinnvoll wären, aber bei manchen, insbesondere bei Funktionen mit Parametern, die sowohl gelesen als auch geschrieben werden, ist die ausschließliche Beschränkung auf den Rückgabewert hinderlich.
-
audacia schrieb:
Konrad Rudolph schrieb:
'sprintf' *kann* theoretisch gar nicht so schnell implementiert werden wie ein gut genutzter Stringstream, weil 'sprintf' zur Laufzeit den Formatstring parsen muss.
Theoretisch, ja. In der Praxis ist sprintf meist nicht nur schneller und kleiner, sondern auch viel einfacher für die Lokalisierung. Und dank der Spracherweiterungen von C++ kann man auch typsichere sprintf-Versionen schreiben.
du meinst variadic templates? die sind aber noch nicht so verbreitet afaik.
struct READFILE_RESULT { BOOL bSuccess; std::vector <BYTE> vBuffer; OVERLAPPED overlappedOut; } READFILE_RESULT MyReadFile (HANDLE hFile, DWORD nNumberOfBytesToRead, OVERLAPPED overlappedIn);ich glaube fast, er bezog sich auf tr1::tuple und nicht auf die WinAPI.
-
Wenns zwei Werte sind, kann man auch schon pair benutzen:
std::pair<bool, std::vector<int>> foo();Wenns mehr sein soll, dann tuple aus dem TR1, wie oben gesagt. Ist das gleiche Prinzip wie pair, nur halt mit mehr möglichen Werten.
Wie immer: die wenigsten kennen die Standardlib und ihre Möglichkeiten und haben deshalb Vorurteile.
-
queer_boy schrieb:
du meinst variadic templates? die sind aber noch nicht so verbreitet afaik.
Ein wenig eingeschränkt und umständlicher geht es ja auch ohne (indem man die Funktion ~20x überlädt). Nicht schön, aber möglich, und es hat, wie du sagst, Potential, bald viel einfacher lösbar zu werden.
queer_boy schrieb:
ich glaube fast, er bezog sich auf tr1::tuple und nicht auf die WinAPI.
Dann denk dir READFILE_RESULT doch einfach als ausgeschriebenes Tuple

-
wie du selbst schon angesprochen hast, könnte man die WinAPI in C++ auch ganz anders schreiben. nur war ich mir nicht mehr sicher, wie sehr ich darauf eingehen sollte, weil man nicht genau erkennen kann, an welcher stelle der sarkasmus beginnt

btw. häng an die ~20 vielleicht noch eine null dran, und für jeden eigenen typ auch noch mal soviele (für alle kombinationen) - variadic templates oder gar nicht erst in die versuchung kommen, sondern gleich die typsicheren streams verwenden.
-
queer_boy schrieb:
btw. häng an die ~20 vielleicht noch eine null dran, und für jeden eigenen typ auch noch mal soviele (für alle kombinationen)
Ungeachtet der Ernsthaftigkeit des Vorschlages: weshalb für jeden eigenen Typ nochmal so viele? Für 0-19 Parameter reichen doch 20 Überladungen, oder?
queer_boy schrieb:
variadic templates oder gar nicht erst in die versuchung kommen, sondern gleich die typsicheren streams verwenden.
Was macht denn boost::function?