Ich hatte gerade etwas über Referenzen nachgelesen -- und es dann zur
Sicherheit noch mal ausprobiert. Ist tatsächlich so wie ich es in der
Erklärung verstanden hatte -- aber schon recht sonderbar.
mh schrieb:> Was ist daran jetzt verrückt oder sonderbar?
das er es verstanden hat! :-) (eventuell ist er ein Spätzünder und das
ist wahrscheinlich ihm bekannt)
Dass konstante Referenzen auf temporäre Werte erlaubt sind, und die
Referenz die Lebensdauer des Werts verlängert. "rx" ist keine Referenz
auf x (geht auch gar nicht weil inkompatible Typen), sondern eine auf
eine "unsichtbare" float-Variable, die einmal mit dem konvertierten Wert
von x initialisiert, aber dann nicht mehr geändert wird.
mh schrieb:> Was ist daran jetzt verrückt oder sonderbar?
Nun, es spricht nicht gerade für eine Sprache, wenn nicht das
herauskommt, was man beim ersten Betrachten des Quelltextes ganz
natürlich erwarten würde.
Solche Sachverhalte zeigen i.d.R., das eine Sprache sinnlos aufgeblasen
ist, bis hin zur potentiellen Unbrauchbarkeit(*).
(*) Das ist der Punkt, wenn es aufwendiger wird, die Sprache zu
kapieren, als das Problem, was man damit lösen möchte. Dieser Punkt war
bei C++ im Moment der Sprachschöpfung bereits in sehr vielen Fällen
erreicht und sogar weit überschritten. Heute ist C++ reine
Klartextverschlüsselung. Eine typische Write-only-Sprache. Es ist
mittlerweile fast unmöglich geworden, aus dem Quelltext auf das damit
angeblich gelöste Problem zu schliessen, selbst wenn man C++ beherrscht
(was allerdings mittlerweile auch fast niemand mehr tatsächlich von sich
behaupten kann)...
c-hater schrieb:> mh schrieb:>>> Was ist daran jetzt verrückt oder sonderbar?>> Nun, es spricht nicht gerade für eine Sprache, wenn nicht das> herauskommt, was man beim ersten Betrachten des Quelltextes ganz> natürlich erwarten würde.>
Das ist aber ein komplett selbst verschuldetes Problem.
Ich codiere seit >> 10 Jahren in C++, aber das ganze Gedöns aus ersten
Beitrag in diesem thread interessiert mich nicht die Bohne. Kann auch
(muss nicht) für Embedded komplett irrelevant sein. Ein stdout (oder
anderer stream) ist ein künstliches Konstrukt, das mit der Sprache
erstmal herzlich wenig zu tun hat.
Eines der Riesenvorteile von C++ ist die Möglichkeit der Einkapselung.
Denke bei C++ an eine Hyperstrukt, die neben den (Daten)Strukturmembers
auch Funktionen haben kann, die implizit Alles was die Instanz der
Strukt hat mit referenziert. Also statt in C zu schreiben
Fn1(MeineStrukt *abc)
{
...abc->member1...
...
abc->member2...
}
und
Fn2(MeineStrukt *abc)
{
...abc->member1...
...
abc->member2...
}
schreibst Du in C++:
class MeineStrukt
{
private <typegal> member1,member2;
...
public:
Fn1(void);
Fn2(void);
}
MeineStrukt::Fn1()
{
member1...
...
member2...
}
MeineStrukt::Fn2()
{
member1...
...
member2...
}
Selbst (gerade) wenn Du den gesamten C++ Schnickschnak wie überladene
Operatoren, Copy Konstruktoren, templates, SEH etc. weglässt, wird
dieser Code gegenüber C um Millionenfach les- und wartbarer. Und kostet
praktisch kein einziges Byte mehr footprint.
Der im TO skizzierte Code ist vielleicht für PC Programmierer
interessant. Für Embedded gehört er zu den Klickibunti Gimmicks von C++,
die man genau so gut weglassen kann und ohne die derselbe Code gegenüber
C von einem wirren Wust zu richtig gut wird.
c-hater schrieb:> Nun, es spricht nicht gerade für eine Sprache, wenn nicht das> herauskommt, was man beim ersten Betrachten des Quelltextes ganz> natürlich erwarten würde.
Wo ist das denn hier der Fall?
Adapter schrieb:> Der im TO skizzierte Code ist vielleicht für PC Programmierer> interessant. Für Embedded gehört er zu den Klickibunti Gimmicks von C++,> die man genau so gut weglassen kann
Referenzen sind PC Klickibunti Zeug? Die Ausgabe per std::cout hat
überhaupt nichts mit dem Problem zu tun. Würdest du per printf()
ausgeben oder die Zahlen direkt in irgendwelche Register schreiben, wäre
das "Problem" das gleiche.
Außerdem ist OOP nur ein kleiner Teil von C++. Da gibts noch viel mehr
was gerade für Embedded gut ist...
Dr. Sommer schrieb:> Adapter schrieb:>> Der im TO skizzierte Code ist vielleicht für PC Programmierer>> interessant. Für Embedded gehört er zu den Klickibunti Gimmicks von C++,>> die man genau so gut weglassen kann>> Referenzen sind PC Klickibunti Zeug? Die Ausgabe per std::cout hat> überhaupt nichts mit dem Problem zu tun. Würdest du per printf()> ausgeben oder die Zahlen direkt in irgendwelche Register schreiben, wäre> das "Problem" das gleiche.>
Konstrukte wie std::cout haben für das, was ICH in Embedded mache,
keinerlei Relevanz. Genau so wenig wie prontf().
> Außerdem ist OOP nur ein kleiner Teil von C++. Da gibts noch viel mehr> was gerade für Embedded gut ist...
Beispiele?
Es ging mir darum, die im ursprünglichen Beitrag (gebetsmühlenartig)
reiterierte Kritik von C++ als nicht mehr echtzeitles- und verstehbare
Sprache zu relativieren und zu zeigen, dass (zumindestens für Leute aus
der C-Welt) eine Untermenge von C++, die wirklich nichts von diesen
Konstrukten wie << (das ist für C Programmierer ein Left shift, was
z.Teufel soll das mit Ausgaben zu tun haben) nur Vorteile bringt.
Adapter schrieb:> Konstrukte wie std::cout haben für das, was ICH in Embedded mache,> keinerlei Relevanz. Genau so wenig wie prontf().
Kann sein. Hat aber nix mit dem Ausgangsposting zu tun. Ersetze das
"std::cout<<" durch irgendeine andere Form der Ausgabe. Auch
Embedded-Programme müssen irgendwie mit der Außenwelt kommunizieren, und
sei es über GPIO-Pins.
Adapter schrieb:> Beispiele?
Insbesondere Template-Metaprogrammierung und constant expressions.
Weniger für Embedded, aber dennoch mächtig wären Exceptions
(OOP-verwandt).
Adapter schrieb:> Es ging mir darum, die im ursprünglichen Beitrag (gebetsmühlenartig)> reiterierte Kritik
Die kann man einfach ignorieren.
Adapter schrieb:> die wirklich nichts von diesen> Konstrukten wie << (das ist für C Programmierer ein Left shift, was> z.Teufel soll das mit Ausgaben zu tun haben) nur Vorteile bringt.
Der << Operator ist vielleicht unglücklich gewählt. Die Grundidee der
typsicheren Ausgabe ist aber ein sehr gutes Konzept und printf()
überlegen. Die C++ Syntax ist ohne Frage schrecklich (dank des Erbes von
C), darüber braucht man nicht zu diskutieren, sondern kann sich der
darunterliegenden Semantik widmen.
Sven B. schrieb:> Adapter schrieb:>>> Außerdem ist OOP nur ein kleiner Teil von C++. Da gibts noch viel mehr>>> was gerade für Embedded gut ist...>>>> Beispiele?>> Templates?
Hab ich noch nie gebraucht (was aber nichts heissen muss).
Vincent H. schrieb:> Ich hab heute etwa folgendes verbrochen
Und was wird da zurückgegeben wenn NDEBUG definiert ist? Wenn das nie
der Fall ist, warum rufst du dann nicht abort() auf, oder wirfst eine
Exception?
Adapter schrieb:> Hab ich noch nie gebraucht (was aber nichts heissen muss).
Die kann man für alles und jedes gebrauchen. Ganz einfaches Beispiel
sind die Funktionen std::min/max, welches das Minimum/Maximum der
übergebenen Zahlen zurückgeben. Sowas geht in C gar nicht (ohne
Seiteneffekte doppelt auszuführen) und in C++ nur sinnvoll mit
templates.
Dr. Sommer schrieb:> Vincent H. schrieb:>> Ich hab heute etwa folgendes verbrochen>> Und was wird da zurückgegeben wenn NDEBUG definiert ist? Wenn das nie> der Fall ist, warum rufst du dann nicht abort() auf, oder wirfst eine> Exception?
Exceptions gibts nicht und abort() würde an jener Stelle im Release
einen Sprung einbaun den ich nicht brauch.
Vincent H. schrieb:> Exceptions gibts nicht und abort() würde an jener Stelle im Release> einen Sprung einbaun den ich nicht brauch.
Und was passiert dann im Release? Rückgabe eines nicht existenten Werts,
d.h. undefined behaviour?
Okay. __builtin_unreachable könnte auch helfen, ist aber
Compiler-Spezifisch. Und ob eine unnötige Sprung-Anweisung im
Programmspeicher jetzt so schlimm ist...
der Thread hat interessant angefangen, langsam geht es aber wieder in
das übliche C++ bashing hier über :( Und wenn Moby den morgen früh
findet is eh vorbei...
Das einzige was mich an dem Beispiel vom TO stört ist das der Compile
keine Warnung wirft bei der impliziten Typkonvertierung in 'const int
&rx = x;', jedenfalls hat mein gcc das mit den gerade aktuellen
Einstellungen nicht gemacht. Ich habe das mal meinem µC ins Flash
geworfen und er kommt zum selben Ergebniss wie der TO. Gut, der Code
wurde um 230 kB grösser, aber was soll der Geiz wenn der STM32F407 512
kB hat? Irgendwie muss man die ja mal testen :-)
Vincent H. schrieb:> Ja C++ erlaubt einem schon manch obskure Dinge. =)>> Ich hab heute etwa folgendes verbrochen>
1
>return[]()->R{assert(false);}();
2
>
>> um den Return-Type einer Funktion zu befriedigen (R entspricht dem zu> retunierenden Typen).
Hä? Was soll das darstellen? Das sollte nichtmal kompilieren, weil du
kein return in dem Lambda hast ... das typische Konstrukt ist "return
{};".
Johannes S. schrieb:> Das einzige was mich an dem Beispiel vom TO stört ist das der Compile> keine Warnung wirft bei der impliziten Typkonvertierung in 'const int> &rx = x;', jedenfalls hat mein gcc das mit den gerade aktuellen> Einstellungen nicht gemacht.
Stefans Beispiel ist etwas künstlich, aber es gibt Fälle, wo bei der
Erstellung einer Referenz die implizite Typkonvertierung und das
automatische Anlegen eines temporären Objekts des neuen Types durchaus
erwünscht ist, bspw. hier:
1
voidfunc(conststd::string&s){
2
// mach was mit s
3
}
4
5
intmain(){
6
std::stringstr;
7
8
// fülle str mit Inhalt
9
10
func(str);// (1)
11
12
func("ein Stringliteral");// (2)
13
}
Das Argument s von func ist als Referenz deklariert, damit beim Aufruf
mit einer std::string-Variable (1) diese nicht kopiert werden muss.
Beim Aufruf mit einem Stringliteral (2) passt jedoch der Datentyp nicht.
Durch die implizite Typkonvertierung in ein temporäres Objekt vom Typ
std::string funktioniert der Aufruf trotzdem. Gäbe es weder implizite
Typkonvertierungen noch automatische temporäre Objekte, müsste explizit
und umständlich eine Variable als "Überbringer" des Strings definiert
werden.
Sven B. schrieb:> Hä? Was soll das darstellen? Das sollte nichtmal kompilieren, weil du> kein return in dem Lambda hast ... ...
Doch das sollte kompilieren. Der Standard sagt, es ist undefined
behaviour, wenn eine Funktion ohne richtigen Rückgabewert zurückkehrt.
Wenn sie nie zurückkehrt ist alles in Ordnung.
Wenn wir beim Thema Referenzen sind hätte ich noch eine Frage: Ich habe
ein Chart Objekt als übergeordnetes, das kann dann PlotAreas erstellen
(Factory). Liefere ich diese besser als Zeiger oder als RefObjekt
zurück? Die Idee ist die nicht wieder kaputtzumachen (kein delete),
deshalb eher Referenzen?
Ich hatte das erst mit Zeigern gemacht, aber mit Referenzen klappt es
auch:
1
// basic chart object
2
Chartchart(tft,0,0,320,200,WHITE);
3
4
// create plot area
5
PlotArea&plotArea=chart.addPlotArea(6*6,0,0,10,
6
tft.color565(128,128,128));
habe schon länger nix mehr mit C++ gemacht, freue mich aber das man es
jetzt so gut auf µCs nutzen kann.
Kommt drauf an, wer das PlotArea-Objekt wieder freigibt. Macht die
"Chart"-Klasse das? Dann ist das in Ordnung so. Macht die Chart-Klasse
das, aber verwaltet die PlotArea-Objekte intern in einem std::vector
(welcher die auch freigeben würde)? Dann geht weder Pointer noch
Referenz, sondern z.B. ein Index.
Soll der Aufrufer das PlotArea freigeben? Dann wäre z.B. std::unique_ptr
sinnvoll, notfalls ein normaler Pointer, keinesfalls eine Referenz.
weil es mehrere PlotAreas per Chart werden sollen wollte ich das noch in
einen vector packen, der kann intern aber doch Pointer auf die PlotAreas
halten? Ob die überhaupt deleted werden sollen wäre auch zu überlegen,
viel Dynamik möchte ich ja auf dem µC nicht, für eine GUI kann man die
besser auf unsichtbar schalten wenn die nicht mehr dargestellt werden
sollen.
Johannes S. schrieb:> weil es mehrere PlotAreas per Chart werden sollen wollte ich das noch in> einen vector packen, der kann intern aber doch Pointer auf die PlotAreas> halten?
Wenn der vector aus Pointern besteht ist's in Ordnung. Wenn es ein
vector<PlotArea> ist werden die Pointer ungültig wenn der vector
verändert wird. Eventuell ist es sinnvoll vector<unique_ptr<PlotArea>>
zu nehmen, und dann normale Pointer nach draußen zu geben.
Johannes S. schrieb:> Ob die überhaupt deleted werden sollen wäre auch zu überlegen,> viel Dynamik möchte ich ja auf dem µC nicht,
Steht denn zum Zeitpunkt des Kompilierens die Anzahl fest? Dann kannst
du die auch einmalig statisch alle allokieren. Freigeben ist sinnvoll
wenn du später weniger brauchst, aber den Speicher für irgendwas anderes
gebrauchen kannst.
Dr. Sommer schrieb:> Eventuell ist es sinnvoll vector<unique_ptr<PlotArea>>> zu nehmen, und dann normale Pointer nach draußen zu geben.
ja, ich sehe schon, ich muss mir die std lib nochmal genauer ansehen,
mit den unigue_ptr wird es am Besten sein.
Dr. Sommer schrieb:> Steht denn zum Zeitpunkt des Kompilierens die Anzahl fest?
ich gehe da in kleinen Schritten voran und sehe mir an was das 'kostet'.
Ein kleines Array mit fixer Anzahl ist vielleicht effizienter, aber so
Sachen mag ich nicht wo man hinterher ellenlange Konfigs mit Konstanten
hat. Und ein Index zu weit geschrieben ewige Fehlersuche bedeuten kann.
Danke schonmal für die Tipps, wenn es sich lohnt stelle ich den Code
hier rein.
Vincent H. schrieb:> Ich hab heute etwa folgendes verbrochen return []() -> R {> assert(false); }();
Was soll das bringen ?
Die Debug-Fassung des Programm stürzt mit einer assertion failure ab,
die Release Version liefert irgendein Return-Wert, je welchen denn, an
den Aufrufer ?
http://www.cplusplus.com/reference/cassert/assert/
Johannes S. schrieb:> Ein kleines Array mit fixer Anzahl ist vielleicht effizienter
Kannst ja die Anzahl per Template-Parameter übergeben. Oder das
Chart-Objekt hat nur einen Pointer auf das Array, welches der Benutzer
selbst (statisch) allokiert.
mh schrieb:> Sven B. schrieb:>> Hä? Was soll das darstellen? Das sollte nichtmal kompilieren, weil du>> kein return in dem Lambda hast ... ...>> Doch das sollte kompilieren. Der Standard sagt, es ist undefined> behaviour, wenn eine Funktion ohne richtigen Rückgabewert zurückkehrt.> Wenn sie nie zurückkehrt ist alles in Ordnung.
Ja naja, aber wo ist jetzt der Vorteil gegenüber return {}?
Sven B. schrieb:> Ja naja, aber wo ist jetzt der Vorteil gegenüber return {}?
return {} könnte einen Konstruktor aufrufen was noch mehr
Programmspeicher als abort() fressen könnte oder es könnte kein
Standard-Konstruktor existieren.
Dr. Sommer schrieb:> Der << Operator ist vielleicht unglücklich gewählt. Die Grundidee der> typsicheren Ausgabe ist aber ein sehr gutes Konzept und printf()> überlegen.
Zur Ehrenrettung von printf und Konsorten sei mal angemerkt, dass
aktuelle Compiler auch hier den Syntax prüfen und die meisten Fehler
auch bemerken.
Für mich ist der << Operator einer der Sachen, die ich in C++ nie Nutze
und deren Nutzen ich auch nicht nachvollziehen kann. Man kann beim Lesen
selbst einer einfachen Ausgabezeile nicht mehr erkennen was überhaupt
ausgegeben wird.
1
stdout<<a<<b<<c;
Zuerst muss man halt nachsehen ob in a, b und c die "<<" Operatoren
überladen wurden und was sie daraus machen. Man blättert dann erst mal
durch mehrere Dateien. Und wenn dann noch jeder Programmierer denkt nur
er hat denn Schuss gehört und seine eigenen Kreationen z.B. zum Zahlen
Formatieren sind die besten, dann ist das unübersichtliche Chaos
perfekt. Da ist mir ein einfaches printf() aber 1000 mal lieber und ich
lasse mich gern in dieses Korsett zwängen. Niemand würde es z.B. gut
finden, wenn die Formatanweisungen von printf() in jedem Programm anders
definiert wären. Das ist aber genau die negative Konsequenz dieser
Freiheit in C++.
Das ganze wird dann noch durch die Metaprogrammiererei mit Templates
getoppt. Es gibt sicherlich einige Programmierer die das gut können, die
meisten sind aber nur der Meinung dass sie das gut können. Es gibt
genügend Beispiele hier im Forum wo aus trivialen Aufgabenstellungen
unleserliche C++ Romane entstanden sind. Von der wunderbaren
Debugbarkeit von Templateorgien will ich gar nicht erst reden.
Programmierer sind ja auch immer Eitel. Und aus dieser Eitelkeit heraus
versuchen sich auch manche zu beweisen. Da wird dann jedes Problem auf
eine Art und Weise gelöst, die man nur selbst versteht. Das vermittelt
dann das überragende Gefühl besser zu sein als die Anderen...
Dr. Sommer schrieb:> Sven B. schrieb:>> Ja naja, aber wo ist jetzt der Vorteil gegenüber return {}?>> return {} könnte einen Konstruktor aufrufen was noch mehr> Programmspeicher als abort() fressen könnte
?
Der Code macht eh keinen Sinn wenn der jemals aufgerufen wird ...
> oder es könnte kein> Standard-Konstruktor existieren.
Dann ist die Lösung delcval<R>() und nicht dieser komische Kram.
Sven B. schrieb:> Dann ist die Lösung delcval<R>() und nicht dieser komische Kram.
Das dacht ich zuerst auch, aber bei der libstdc++ Implementierung kommt
dann der __declval_protector und meint "you shall not pass" ;)
temp schrieb:> Für mich ist der << Operator einer der Sachen, die ich in C++ nie Nutze> und deren Nutzen ich auch nicht nachvollziehen kann. Man kann beim Lesen> selbst einer einfachen Ausgabezeile nicht mehr erkennen was überhaupt> ausgegeben wird.
Das kann ich nachvollziehen. Ich selbst nutze "iostream" nur bei sehr
sehr einfachen Sachen. Zum Beispiel wenn ich zum debuggen eine Variable
ausgebe.
Bei allem was komplizierter ist und wo es tatsächlich auf die
Formatierung ankommt benutze ich Alternativen, von denen es ja zum Glück
genug gibt.
Mein Favorit ist die fmt Bibliothek (http://fmtlib.net/latest/). Die
kann alles was ich brauche und unterstützt neben der printf auch die
python.format Syntax. Und evtl. wird sie auch irgendwann in den c++
Standard integriert, zumindest ein Proposal gibt es.
Vincent H. schrieb:> Sven B. schrieb:>> Dann ist die Lösung delcval<R>() und nicht dieser komische Kram.>> Das dacht ich zuerst auch, aber bei der libstdc++ Implementierung kommt> dann der __declval_protector und meint "you shall not pass" ;)
Wie erwartet ist die Implementierung korrekt. Der Standard sagt:
1
23.2.7 Function template declval [declval]
2
1 The library provides the function template declval to simplify the definition of expressions which occur as unevaluated operands (Clause 8).
2 Remarks: If this function is odr-used (6.2), the program is ill-formed.
6
3 Remarks: The template parameter T of declval may be an incomplete type.
7
4 [ Example:
8
template <class To, class From> decltype(static_cast<To>(declval<From>())) convert(From&&);
9
declares a function template convert which only participates in overloading if the type From can be explicitly converted to type To. For another example see class template common_type (23.15.7.6). — end example ]
Wichtig ist Remark 2.
Der Standard (C++17 Draft n4687 6.2.3) sagt zu odr-used functions:
1
... A function whose name appears as a potentially-evaluated expression is odr-used if it is the unique lookup result or the selected member of a set of overloaded functions (6.4, 16.3, 16.4), unless it is a pure virtual function and either its name is not explicitly qualified or the expression forms a pointer to member (8.3.1). [ Note: This covers calls to named functions (8.2.2), operator overloading (Clause 16), user-defined conversions (15.3.2), allocation functions for placement new-expressions (8.3.4), as well as
2
non-default initialization (11.6). A constructor selected to copy or move an object of class type is odr-used even if the call is actually elided by the implementation (15.8). — end note ] ...
temp schrieb:> Zur Ehrenrettung von printf und Konsorten sei mal angemerkt, dass> aktuelle Compiler auch hier den Syntax prüfen und die meisten Fehler> auch bemerken.
Es sei denn man baut sich eigene Varianten dafür... Selbst beim
Durchleiten mit variadischen templates funktioniert beim GCC diese
Prüfung nicht mehr.
temp schrieb:> Man kann beim Lesen> selbst einer einfachen Ausgabezeile nicht mehr erkennen was überhaupt> ausgegeben wird.
Ich finde das super. Wenn ich einen Datentyp ausgeben will, schreib ich
"<<" hin und fertig. Ich muss mir nicht erst überlegen welche Funktionen
dafür aufgerufen werden müssen.
Ein Beispiel:
Der "<<" Operator wird für Point überladen, jetzt kann ich das direkt
ausgeben. Wenn ich jetzt eine template-Klasse habe (hier:
std::vector<T>), kann ich die enthaltenen Elemente genauso ausgeben ohne
mir Gedanken zu machen wie bestimmte Typen jetzt ausgegeben werden
sollen. Das wird hier genutzt um beliebige Vektoren ausgeben zu können.
Das wäre ohne Überladen des "<<" Operators doch ziemlich umständlich. In
der main()-Funktion kann ich dann Vektoren von int genauso ausgeben wie
Vektoren von Point. So etwas ist super für verschachtelte generische
Datenstrukturen.
temp schrieb:> Zuerst muss man halt nachsehen ob in a, b und c die "<<" Operatoren> überladen wurden und was sie daraus machen. Man blättert dann erst mal> durch mehrere Dateien.
Das ist der Sinn von Abstraktion. Details werden verpackt, und nur
wenn man die genau wissen will schaut man in der Implementation nach.
Die meisten höheren Programmiersprachen nutzen das ausführlich. Wenn du
das nicht willst, solltest du alles in Assembler programmieren und
natürlich niemals Funktionen benutzen. Bei printf() siehst du ja auch
nicht wie die Ausgabe jetzt genau durchgeführt wird. Du weißt es
höchstens dank der Dokumentation von printf() - aber eigene "<<"
Überladungen darf man auch dokumentieren.
temp schrieb:> Das ist aber genau die negative Konsequenz dieser> Freiheit in C++.
Man kann sich auch eigene Zahlen-Ausgaben in C mit puts() basteln. Die
Nutzung der IO-Streams hat definitiv nichts damit zu tun ob man sich
eigene Zahlenformate baut. Ich hab jedenfalls noch nie gesehen dass das
jemand tun würde.
In C muss ich immer Angst haben dass die Format-Specifier von printf()
falsch sind, sobald man den Typ der auszugebenden Variable ändert (z.B.
time_t könnte sich von 32 auf 64bit ändern), und dann der Code nicht
mehr funktioniert. Das kann bei den IO-Streams nicht passieren. Und ja,
die IO-Streams bieten genau so flexible Möglichkeiten zur Formatierung
wie printf().
Der einzige wirkliche Nachteil der IO-Streams ist dass die Strings
zerpflückt werden, was schlecht für Übersetzungen ist.
Da ist sowas eine Lösung, was ja - genau wie IO-Streams - ebenfalls
typsicher ist (und ebenfalls erweitert werden kann, nicht über "<<" aber
über format_arg, mit einem ähnlichen Ergebnis):
mh schrieb:> Mein Favorit ist die fmt Bibliothek (http://fmtlib.net/latest/).
Dr. Sommer schrieb:> Da ist sowas eine Lösung, was ja - genau wie IO-Streams - ebenfalls> typsicher ist (und ebenfalls erweitert werden kann, nicht über "<<" aber> über format_arg, mit einem ähnlichen Ergebnis):> mh schrieb:>> Mein Favorit ist die fmt Bibliothek (http://fmtlib.net/latest/).
fmt kann auch mit ostream und operator<< für user-defined types
erweitert werden: http://fmtlib.net/latest/api.html#std-ostream-support
Es kann also für alle Typen die schon einen operator<< für die
std::streams haben direkt genutzt werden. Nur für Typen die eine
parameterisierbare Formatierung benötigen muss man sich um format_arg
kümmern.
Dr. Sommer schrieb:> Es sei denn man baut sich eigene Varianten dafür... Selbst beim> Durchleiten mit variadischen templates funktioniert beim GCC diese> Prüfung nicht mehr.
Zeit für die selbsternannten C++ Gurus auch mal wieder etwas über C zu
lernen:
https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Function-Attributes.html
Ein einfaches Attribut sagt dem gcc dass er diese Funktion im printf
Style überprüfen soll.
ist so oder ähnlich seit 30 Jahren in jedem C++ Buch zu finden und ein
hervorragendes Beispiel für lesbaren Code... Und ich für meinen Teil
hasse das seit genau so vielen Jahren. Du kannst es ja gut finden, das
ändert aber nichts an meiner Meinung. Ich hab die Erfahrung gemacht,
gepuscht wird das vor allem von Dozenten die sich an so was wie einen
"Lehrplan" halten.
temp schrieb:> Zeit für die selbsternannten C++ Gurus auch mal wieder etwas über C zu> lernen:
Das Attribut kenne ich, und es ist eine Compiler-Erweiterung. Eine
elegante Lösung ist das nicht gerade, sie basiert darauf dass der
Compiler printf() und seine Funktionsweise kennt. Ein Compiler sollte
normalerweise keine einzelnen Funktionen direkt unterstützen - so etwas
sollte von innerhalb der Sprache möglich sein. Wenn man eigene
Format-Specifier nachrüsten möchte (z.B. für eigene Datentypen) klappt
das schon gar nicht mehr.
temp schrieb:> ist so oder ähnlich seit 30 Jahren in jedem C++ Buch zu finden und ein> hervorragendes Beispiel für lesbaren Code...
Wie gesagt, das "<<" ist fragwürdig. Die grundlegende Idee, Overloads zu
nutzen, finde ich äußerst sinnvoll. Bei Arduino z.B. heißt es statt "<<"
halt "print", mit ähnlicher Semantik. Findest du das dann besser?
temp schrieb:> Ich hab die Erfahrung gemacht,> gepuscht wird das vor allem von Dozenten die sich an so was wie einen> "Lehrplan" halten.
Vielleicht kann man von Dozenten ja noch was lernen, dafür sind die
schließlich da. Wer möchte dass der Code "hübsch" aussieht, sollte kein
C++ nutzen. Es ist nicht schön, aber flexibler und sicherer als
printf().
Dr. Sommer schrieb:> Bei Arduino z.B. heißt es statt "<<"> halt "print", mit ähnlicher Semantik. Findest du das dann besser?
Bei Arduino bestimmt aber die Print Klasse die Formattierung, beim <<
Operator wird die vom auszugebenden Objekt erstellt. Das Print müsste
man um die neuen Typen erweitern, finde ich nicht schön.
Bezieht ihr euch bei euren iostream Beispielen auf PC oder µC? Ich hatte
ja gestern das auf dem µC eingebunden und der Code wuchs um +230 kB
(Debug Release mit newlib). Das erscheint mir sehr viel, ist diese
Grössenordnung normal?
Johannes S. schrieb:> Bei Arduino bestimmt aber die Print Klasse die Formattierung,
Nur bei vordefinierten Typen - genau wie bei io streams.
Johannes S. schrieb:> Das Print müsste man um die neuen Typen erweitern, finde ich nicht> schön.
Einfach einen Overload hinzufügen, genau wie bei io streams. Das ist
absolut gängiges Vorgehen in C++, dafür gibt es ja auch ADL. Hier rächt
sich aber das schlechte Design der Arduino Library, nämlich dass die
Print Funktionen in einer Klasse sind...
Johannes S. schrieb:> Bezieht ihr euch bei euren iostream Beispielen auf PC oder µC
PC. Die gängigen Implementationen der IOStreams sind nicht uC geeignet.
Dr. Sommer schrieb:> Vielleicht kann man von Dozenten ja noch was lernen, dafür sind die> schließlich da.
Wenn Dozenten ein simples Beispiel aufschreiben wie du:
Da wird ohne Not einfach ein vector benutzt der dynamischen Speicher
belegt. Wir sind hier in einem µC Forum, also da wo es noch darum geht
mit den Resourcen zu knausern. Die Anfänger übernehmen dass und
programmieren in Zukunft in dem Stil weiter ohne darüber Nachzudenken
was etwas kostet. Leider kann man auch kaum noch abschätzen was am Ende
ein Template kostet. Deshalb macht sich auch niemand mehr Gedanken
darüber, dafür werden die Programme immer fetter und damit auch nicht
sicherer.
Dr. Sommer schrieb:> Bei Arduino z.B. heißt es statt "<<"> halt "print", mit ähnlicher Semantik. Findest du das dann besser?
Nein, Arduino ist ein ganz schlechtes Beispiel. Gerade weil es da in der
Lib auch ziemlich inkonsequent gehandhabt wird. Wenn man da versucht
z.B. auf dem ESP8622 ein simples Internetprotokoll (IMAP, MAIL o.ä.) mit
Ausgaben zu Debuggen weil's anders nicht geht, kriegt man die Motten.
Die Libs und der Core verwenden schon gemischt "\r\n" und "\n" als
Zeilenende. Die println-Funktion nimmt std::endl. Die IMAP oder SMTP
Strings wollen "\r\n" am Zeilenende. Am Ende ist die Ausgabe im Terminal
immer irgendwie zerrupft.
temp schrieb:> Wir sind hier in einem µC Forum, also da wo es noch darum geht mit den> Resourcen zu knausern.
Wir sind im Unterforum "PC-Programmierung" ;-)
temp schrieb:> Da wird ohne Not einfach ein vector benutzt der dynamischen Speicher> belegt.
Gah, das war ein Beispiel. Wenn man nur genau diese Ausgabe erreichen
will, kann man auch
1
std::fputs("42 3.14 Point {3, 4} {1, 2, 3} {Point {2, 3}, Point {4, 5}, Point {6, 7}}",stdout);
schreiben. Manchmal hat man halt Vektoren, und das wäre eine Möglichkeit
sie flexibel auszugeben. Tausche std::vector gegen std::array und du
hast das gleiche in Nicht-Dynamisch.
temp schrieb:> Leider kann man auch kaum noch abschätzen was am Ende> ein Template kostet.
Genau so viel wie entsprechender C-Code.
temp schrieb:> Nein, Arduino ist ein ganz schlechtes Beispiel.
Okay, lassen wir Arduino, machen wir das mal so wie im Anhang gezeigt -
eine einfache Bibliothek zur Ausgabe von Integern und Strings. Der
Einfachheit halber geben wir die Zahlen per printf() aus. In echt würde
man da einen entsprechenden Algorithmus einbauen.
Bei Variante 1 haben wir Funktions-Overloads namens "print" ähnlich zu
Arduino. Bei Variante 1.5 werden die noch etwas kompakter mit einer
variadischen Funktion aufgerufen.
Bei Variante 2 wird wie bei IO-Streams der "<<" Operator überladen. In
allen Versionen kann man jetzt problemlos Overloads für eigene Klassen,
hier "Point", hinzufügen und alles über ein einheitliches Interface
ausgeben. Welche Variante findest du jetzt besser?
Die I/O-Streams in C++ sind manchmal auf Grund ihrer Erweiterbarkeit
sehr praktisch. So kann man bspw. in der OpenCV eine Matrix ganz einfach
mit
1
std::cout<<matrix;
ausgeben.
Der absoluten Horror ist es aber, damit gewöhnliche Zahlenwerte als
sauber formatierte Tabelle auszugeben:
- Das kleinste Problem ist dabei noch der <<-Operator, der zu sperrig
ist, nicht nur vor jeder auszugebenden Variable, sondern zusätzlich
vor jedem Formatierungselement benötigt wird, einen Missbrauch des
Bitshift-Operators darstellt und deswegen auch die falsche Präzedenz
hat.
- Die Syntax ist insgesamt an Umständlichkeit und Unübersichtlichkeit
kaum zu überbieten.
- Manche der Formatierungsparameter gelten nur jeweils für das nächste
auszugebende Element (z.B. std::setw) und müssen deswegen u.U. laufend
wiederholt werden.
- Andere (z.B. std::setprecision) sind persistent und müssen nach
erfolgter Nutzung explizit rückgängig gemacht werden, um nicht andere
Ausgaben im Programm zu beeinflussen.
Hier zwei Beispiele:
1
#include<iostream>
2
#include<iomanip>
3
#include<cstdio>
4
5
intmain(){
6
7
// Beispiel 1, Tabellezeile mit nummerierten dreidimesionalen Koordinaten
Die Printf-Varianten sind nicht nur sehr viel kürzer, man sieht auch auf
einen Blick, welche Variablen überhaupt ausgegeben werden. Bei etwas
genauerer Betrachtung des Formatstrings kann man sich sogar vorstellen,
wie die ausgegebene Tabellenzeile ungefähr aussehen wird. Das alles geht
in der cout-Variante völlig unter.
Deswegen benutze ich I/O-Streams nur dann, wenn ich wirklich auf ihre
Erweiterbarkeit angewiesen bin. In den 95% aller Fälle, wo dies nicht
der Fall ist, ist printf für mich ganz klar die bessere Alterenative.
Dr. Sommer schrieb:> Welche Variante findest du jetzt besser?
Ziel ist es nicht eine Aufgabe zu finden für die deine Lösung besser
passt.
Yalu X. schrieb:> Der absoluten Horror ist es aber, damit gewöhnliche Zahlenwerte als> sauber formatierte Tabelle auszugeben:
Dem und den ganzen anderen was du geschrieben hast kann ich voll
zustimmen.
Es geht auch nicht darum die Ausgabe über Streams in C++ komplett zu
verteufeln. Umgedreht kann ich aber nicht akzeptieren, dass es nur noch
über diesen Weg gehen muss und allen, die das nicht so sehen, Ignoranz,
Dummheit oder Lernfaulheit unterstellt wird.
temp schrieb:> Ziel ist es nicht eine Aufgabe zu finden für die deine Lösung besser> passt.
Das ist nicht "meine" Lösung, und ich wollte nur versuchen
herauszufinden, was genau dein Problem damit ist.