Kann der Compiler das optimieren?
-
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?
-
audacia schrieb:
queer_boy schrieb:
variadic templates oder gar nicht erst in die versuchung kommen, sondern gleich die typsicheren streams verwenden.
Was macht denn boost::function?
Was hat das mit 'boost::function' zu tun? Die sind doch typensicher.
Btw, zur Tupel-Rückgabe: natürlich ist die WinAPI eingeschränkt, da sie eine sehr typenbeschränkte Schnittstelle darstellt, um möglichst vielseitig einsetzbar zu sein. Dass man auch heute noch größtenteils an einer C-Schnittstelle für jegliches Interop hängt, ist natürlich extrem schade, weil es doch die Möglichkeiten extrem beengt.
Die WinAPI ist ja geradezu das Paradebeispiel, wie man es nicht machen sollte. Sowas passiert eben, wenn man versucht, in C OOP zu programmieren.
-
Konrad Rudolph schrieb:
Was hat das mit 'boost::function' zu tun? Die sind doch typensicher.
boost::function ist nicht mittels Variadic Templates, sondern über die Mehrfachüberladung implementiert, vor der queer_boy mich so dringend warnte.
Konrad Rudolph schrieb:
Btw, zur Tupel-Rückgabe: natürlich ist die WinAPI eingeschränkt, da sie eine sehr typenbeschränkte Schnittstelle darstellt, um möglichst vielseitig einsetzbar zu sein.
Vielleicht hätte ich den Sarkasmus noch deutlicher durchblicken lassen sollen.
Hältst du meine ReadFile-Versionen im Ernst für in irgendeiner Weise praxistauglich?Konrad Rudolph schrieb:
Dass man auch heute noch größtenteils an einer C-Schnittstelle für jegliches Interop hängt, ist natürlich extrem schade, weil es doch die Möglichkeiten extrem beengt.
Die WinAPI ist ja geradezu das Paradebeispiel, wie man es nicht machen sollte. Sowas passiert eben, wenn man versucht, in C OOP zu programmieren.
Das sehe ich anders. Ein Betriebssystem wie Windows _sollte_ IMHO eine C-Schnittstelle haben und nicht eine, die es auf eine der Hochsprachen und ein ABI beschränkt - es sei denn, es ist für mehrere Sprachen entworfen wie das .NET-Framework, und selbst das ist ja in C++ nur mit einer gewaltigen Verrenkung namens C++/CLI verwendbar.
-
audacia schrieb:
Konrad Rudolph schrieb:
Was hat das mit 'boost::function' zu tun? Die sind doch typensicher.
boost::function ist nicht mittels Variadic Templates, sondern über die Mehrfachüberladung implementiert, vor der queer_boy mich so dringend warnte.
Ja, weil es variadic templates eben noch nicht gibt.
Konrad Rudolph schrieb:
Btw, zur Tupel-Rückgabe: natürlich ist die WinAPI eingeschränkt, da sie eine sehr typenbeschränkte Schnittstelle darstellt, um möglichst vielseitig einsetzbar zu sein.
Vielleicht hätte ich den Sarkasmus noch deutlicher durchblicken lassen sollen.
Hältst du meine ReadFile-Versionen im Ernst für in irgendeiner Weise praxistauglich?Nein, die Version ist natürlich schlecht und man würde die ganz anders implementieren. Aber ich halte trotzdem keine Version mit Ausgabe-Parametern für sinnvoll. Übrigens benutzt die STL Mechanismen, die recht ähnlich sind, z.B. die 'insert'-Methode von Containern.
Konrad Rudolph schrieb:
Dass man auch heute noch größtenteils an einer C-Schnittstelle für jegliches Interop hängt, ist natürlich extrem schade, weil es doch die Möglichkeiten extrem beengt.
Die WinAPI ist ja geradezu das Paradebeispiel, wie man es nicht machen sollte. Sowas passiert eben, wenn man versucht, in C OOP zu programmieren.
Das sehe ich anders. Ein Betriebssystem wie Windows _sollte_ IMHO eine C-Schnittstelle haben und nicht eine, die es auf eine der Hochsprachen und ein ABI beschränkt - es sei denn, es ist für mehrere Sprachen entworfen wie das .NET-Framework, und selbst das ist ja in C++ nur mit einer gewaltigen Verrenkung namens C++/CLI verwendbar.
Ich habe ja auch nicht gesagt, dass man es besser machen könnte. Die WinAPI ist gewissermaßen das kleinste Übel. Trotzdem ist sie schlimm.
-
Konrad Rudolph schrieb:
Ja, weil es variadic templates eben noch nicht gibt.
Genau darum sehe ich im Gegensatz zu queer_boy auch keine Verwerflichkeit darin, sich, solange das der Fall ist, dieses Hilfsmittels zu bedienen.
Konrad Rudolph schrieb:
Aber ich halte trotzdem keine Version mit Ausgabe-Parametern für sinnvoll. Übrigens benutzt die STL Mechanismen, die recht ähnlich sind, z.B. die 'insert'-Methode von Containern.
Du bist dir der Einschränkung, der man sich unterzieht, wenn man nicht direkt in einen Puffer lesen lassen kann, weil man den als Parameter übergeben müßte, bewußt?
Konrad Rudolph schrieb:
Ich habe ja auch nicht gesagt, dass man es besser machen könnte. Die WinAPI ist gewissermaßen das kleinste Übel. Trotzdem ist sie schlimm.
Achso, du meinst so ähnlich wie bei Churchill und der Demokratie? Dann kann ich dir natürlich beipflichten.

-
Konrad Rudolph schrieb:
Learning Java: Better never than late.
Dabei kannst du in Java einfach beliebig Pointer auf Klassen zurückgeben und der GC kümmert sich um den Rest. Past doch besser zu deinen Wünschen als C++.
-
audacia schrieb:
Konrad Rudolph schrieb:
Aber ich halte trotzdem keine Version mit Ausgabe-Parametern für sinnvoll. Übrigens benutzt die STL Mechanismen, die recht ähnlich sind, z.B. die 'insert'-Methode von Containern.
Du bist dir der Einschränkung, der man sich unterzieht, wenn man nicht direkt in einen Puffer lesen lassen kann, weil man den als Parameter übergeben müßte, bewußt?
Hmm, nein, ehrlich gesagt gerade nicht. Kann sein, dass ich auf dem Schlauch stehe aber was hindert einen daran, einen Proxy zurückzugeben, der operator= überlädt und erst zum Zeitpunkt der Zuweisung das Auslesen übernimmt?
Konrad Rudolph schrieb:
Ich habe ja auch nicht gesagt, dass man es besser machen könnte. Die WinAPI ist gewissermaßen das kleinste Übel. Trotzdem ist sie schlimm.
Achso, du meinst so ähnlich wie bei Churchill und der Demokratie?
Jupp, genauso. Das Leben [eines Programmierers] besteht aus faulen Kompromissen.

haha. schrieb:
Konrad Rudolph schrieb:
Learning Java: Better never than late.
Dabei kannst du in Java einfach beliebig Pointer auf Klassen zurückgeben und der GC kümmert sich um den Rest. Past doch besser zu deinen Wünschen als C++.
Ich halte mich da ganz nach Stepanov. C++ mag fehlerbehaftet sein (ich *hasse* diese Syntax), ist aber trotzdem brillant. Java ist einfach uninteressant. „… it has no intellectual value whatsoever.“ Natürlich muss man immer vorsichtig sein, wenn man einen Polemiker wie Stepanov zitiert, aber konziser kann man es einfach nicht sagen. (Für die anderen: ich halte jetzt meinen Mund. Das wird kein Sprachen-Krieg-Thread.)
-
Konrad Rudolph schrieb:
Ich halte mich da ganz nach Stepanov. C++ mag fehlerbehaftet sein (ich *hasse* diese Syntax), ist aber trotzdem brillant.
naja, sagen wir mal C++ spricht den spieltrieb mancher leute an. brilliant ist aber was anderes.

-
audacia schrieb:
Konrad Rudolph schrieb:
Ja, weil es variadic templates eben noch nicht gibt.
Genau darum sehe ich im Gegensatz zu queer_boy auch keine Verwerflichkeit darin, sich, solange das der Fall ist, dieses Hilfsmittels zu bedienen.
diese diskussion drehte sich ja zunächst darum, ob snprintf eine gute alternative zu stringstreams ist. daraufhin meinte jemand, das stringstreams typensicher sind, ein großer vorteil - und dann wurde die gesamt diskussion sehr hypothetisch. der punkt war nicht der, dass funktionsüberladungen hässlich sind, sondern dass es stringstreams gibt. mehr wollte ich nicht sagen, glaube mir

oder in deinen worten: es ist doch nicht verwerflich, sich dieses hilfsmittels zu bedienen.
aber damit klinke ich mich jetzt hier aus.
-
Konrad Rudolph schrieb:
Hmm, nein, ehrlich gesagt gerade nicht. Kann sein, dass ich auf dem Schlauch stehe aber was hindert einen daran, einen Proxy zurückzugeben, der operator= überlädt und erst zum Zeitpunkt der Zuweisung das Auslesen übernimmt?
Gar nicht ungeschickt soweit.
Zufällig hatte ich gerade Lust dazu:#include <windows.h> #include <vector> #include <iterator> class File { private: HANDLE hFile; DWORD pos; public: template <typename T> class FilePart { friend class File; private: HANDLE file; DWORD pos; DWORD cnt; FilePart (HANDLE hFile, DWORD dwPos, DWORD dwCnt) : file (hFile), pos (dwPos), cnt (dwCnt) {} public: class const_iterator; friend class const_iterator; class const_iterator { friend class FilePart; private: const FilePart* fp; DWORD pos; const_iterator (const FilePart* filepart, DWORD dwPos) : fp (filepart), pos (dwPos) {} public: typedef int difference_type; typedef T value_type; typedef T* pointer; typedef T& reference; typedef std::input_iterator_tag iterator_category; const_iterator& operator ++ (void) { pos += sizeof (T); } const_iterator& operator -- (void) { pos -= sizeof (T); } const_iterator& operator ++ (int) { const_iterator rv (*this); pos += sizeof (T); return rv; } const_iterator& operator -- (int) { const_iterator rv (*this); pos -= sizeof (T); return rv; } bool operator == (const_iterator& rhs) { return (fp == rhs.fp) && (pos == rhs.pos); } bool operator != (const_iterator& rhs) { return (fp != rhs.fp) || (pos != rhs.pos); } T operator * (void) const { T rv; DWORD nobr; OVERLAPPED ov; ZeroMemory (&ov, sizeof (ov)); ov.Offset = pos; ReadFile (fp->file, &rv, sizeof (T), &nobr, &ov); return rv; } // T* operator -> (void) const wird aus naheliegenden Gründen // nicht implementiert }; const_iterator begin (void) const { return const_iterator (this, pos); } const_iterator end (void) const { return const_iterator (this, pos + cnt * sizeof (T)); } }; // ... File (const char* lpFilename, DWORD dwAccess = GENERIC_READ, DWORD dwShareMode = FILE_SHARE_READ, DWORD dwCreationDisposition = OPEN_ALWAYS) : pos (0) { hFile = CreateFile (lpFilename, dwAccess, dwShareMode, NULL, dwCreationDisposition, 0, NULL); } template <typename T> FilePart <T> read (DWORD dwCnt) { union u { T t; }; // schlägt fehl, wenn T nicht trivial ist DWORD opos = pos; // evtl. Bereichsüberprüfungen pos += dwCnt * sizeof (T); return FilePart <T> (hFile, opos, dwCnt); } }; int main (void) { File file ("myFile.bin"); std::vector <int> v; File::FilePart <int> fp = file.read <int> (42); v.insert (v.begin (), fp.begin (), fp.end ()); return 0; }Wirklich wunderschön, das gebe ich zu.
Nur:
#include <windows.h> #include <vector> int main (void) { std::vector <int> v; HANDLE hFile; DWORD nobr; v.resize (42); hFile = CreateFile (lpFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL); ReadFile (hFile, &*(v.begin ()), sizeof (T) * 42, &nobr, NULL); }Irgendwie ist das hier (bzw. eine etwas mehr gekapselte, exceptionsichere Version hiervon) dann doch zu bevorzugen, meinst du nicht? Schon weil ReadFile 41-mal weniger ausgeführt werden muß
Und auch sonst ist das ein bißchen wartungsfreundlicher.Konrad Rudolph schrieb:
Das Leben [eines Programmierers] besteht aus faulen Kompromissen.

... und großen Momenten
