Hallo, ich habe zwei C++ Anfängerfragen: Es geht darum, den Code für ein
Spiel, das ich programmiert habe, zu optimieren. Programmiert wird für
einen ATMega2560 mit dem Arduino Framework (via VS Code und PlatformIO).
Im Moment werden alle Parameter als Wert an Funktionen übergeben. Zwar
werden die Parameter in den Funktionen nicht verändert, es wäre aber
IMHO sauberer, die Parameter als Referenz zu übergeben.
Das Problem ist nun, dass Funktionen mit Variablen oder konstanten
Werten aufgerufen werden (z.B.
1
SetAllPixel(TRUE);
) Somit aktzeptiert der Compiler die Deklaration nicht. Wie löst man das
Problem sauber? Doch beim call by value bleiben?
Und noch eine Frage zu Klassen. Wäre es angemessen, alle Funktionen des
Spiels in eine Klasse zu packen? Von der Klasse müsste eh nur eine
Instanz erzeugt werden, aber wäre es sauberer, dies mit einer Klasse zu
lösen, anstatt den gesamten Code in einer cpp-Datei unterzubringen?
Danke vorab und schöne Grüße!
Michael schrieb:> Hallo, ich habe zwei C++ Anfängerfragen: Es geht darum, den Code für ein> Spiel, das ich programmiert habe, zu optimieren. Programmiert wird für> einen ATMega2560 mit dem Arduino Framework (via VS Code und PlatformIO).>> Im Moment werden alle Parameter als Wert an Funktionen übergeben. Zwar> werden die Parameter in den Funktionen nicht verändert, es wäre aber> IMHO sauberer, die Parameter als Referenz zu übergeben.
Solche Optimierungen sind selten sinnvoll. Im Gegenteil. Kleine Objekte
wie etwa einen Integer sollte man lieber via Kopie übergeben als via
Referenz.
> Das Problem ist nun, dass Funktionen mit Variablen oder konstanten> Werten aufgerufen werden (...) Somit> aktzeptiert der Compiler die Deklaration nicht. Wie löst man das Problem> sauber? Doch beim call by value bleiben?
Dieses Problem bestätigt meine Vermutung dass dein Vorhaben eher eine
Pessimierung wäre. Wenn die Deklaration nach deinen Änderungen
folgendermaßen aussieht
1
voidSetAllPixel(bool&);
dann signalisierst du damit, dass der Übergabeparameter innerhalb der
Funktion verändert wird. Im Gegensatz zur Kopie
1
voidSetAllPixel(bool);
sorgt dies für Code der nicht nur langsamer, sondern auch deutlich
schlechter lesbar ist.
Womit du dich trotzdem beschäftigten solltest wären die Begriffe lvalue,
rvalue und generell value categories. Es ist nämlich sehr wohl möglich
einen sogenannten rvalue (in deinem Fall true) an einen lvalue
(Referenz) zu binden. So kann man folgende Funktion etwa mit true
aufrufen.
1
voidSetAllPixel(boolconst&);
Wie vorher bereits erwähnt macht das Binden an eine konstante Referenz
bei großen Objekten durchaus Sinn, aber niemals, NIEMALS solltest du
eine (nicht-konstante) Referenz statt einer Kopie verwenden.
> Und noch eine Frage zu Klassen. Wäre es angemessen, alle Funktionen des> Spiels in eine Klasse zu packen? Von der Klasse müsste eh nur eine> Instanz erzeugt werden, aber wäre es sauberer, dies mit einer Klasse zu> lösen, anstatt den gesamten Code in einer cpp-Datei unterzubringen?
Vermutlich nicht. C++ ist nicht Java. Nicht alles muss eine Klasse sein.
Wenn schon, dann wird dein Spiel vermutlich aus vielen kleinen Klassen
bestehen, die alle für sich kleine Teilbereiche deines Programms
abdecken und jeweils eine Anforderung erfüllen. Das nennt sich im
Fachjargon das "single responsibility principle".
Vincent H. schrieb:> Solche Optimierungen sind selten sinnvoll. Im Gegenteil. Kleine Objekte> wie etwa einen Integer sollte man lieber via Kopie übergeben als via> Referenz.
Am Anfang hilft hier ggf. eine Regel:
- für eingebaute DT: pass-by-value
- für user-defined-types: pass-by-const-ref
Vincent H. schrieb:> Wenn die Deklaration nach deinen Änderungen> folgendermaßen aussieht> void SetAllPixel(bool&);
So etwas nennt man dann oft "output-parameter". Dies sollte die Ausnahme
sein.
Zusammengefasst:
1
voidfoo(int);// Input-Parameter
2
voidfoo(constA&);// Input-Parameter
3
4
voidbar(int&);// Output-Parameter
5
voidbar(A&);// Output-Parameter
Vielleicht noch ein kleiner Hinweis, der aber u.U. für Anfänger ganz
nützlich ist. Folgender kleiner Fehler
1
voidfoo(int);// in Header
2
3
voidfoo(intv){
4
if(v=4){...}// ups (nur warnung)
5
}
lässt sich ganz einfach vermeiden:
1
voidfoo(int);// in Header
2
3
voidfoo(constintv){
4
if(v=4){...}// error
5
}
Man setzt also in der Definition ein const hinzu. Damit hat man
dieselbe Qualität wie bei pass-by-const-ref. (Natürlich warnt der
Compiler bei dem obigen Missgeschick, aber viele, gerade Anfänger,
nutzen nicht die höchste Warnstufe des Compilers).
Wilhelm M. schrieb:> void foo(int v) {> if (v = 4) {...} // ups (nur warnung)> }
Warum eine Warning? Ich kann doch lokal mit dem v machen was ich will.
Es ist nur eine Kopie.
:-<<<
Wenn du den Fehler der Zuweisung bei if meinst, schreibt man das
anders herum, also erst die Konstante.
if (4 = V) // richtig soll es 4 == v heissen
Man, man, man, ...
:-<<<
Danke vielmals für die Antworten, das hilft mir sehr weiter!
Vincent H. schrieb:> dann signalisierst du damit, dass der Übergabeparameter innerhalb der> Funktion verändert wird. Im Gegensatz zur Kopie> void SetAllPixel(bool);
Ich dachte es ist genau anders herum, denn bei void SetAllPixel(bool);
muss ja eben erst eine Kopie erstellt werden...
Guest schrieb:> Schöner wäre auch die eingebauten Datentypen bool und die Konstanten> true/false statt irgendwelchen großgeschriebenen Sachen zu verwenden.
Das stimmt natürlich, im richtigen Code hatte ich es klein geschrieben,
im Beispiel dann dummerweise groß.
Du betrachtest die Dinge stets aus der Sicht der Funktion. Die ist in
der Praxis jedoch wesentlich unwichtiger als jene von außerhalb.
Eine Kopie signalisiert dem Nutzer der Funktion, dass seine eigenen
Variablen nicht angefasst werden und sich folglich auch nicht ändern
können. Was in der Funktion passiert ist dem Aufrufer egal.
Das ist auch der Grund weshalb man das const keyword so selten bei pass
by value sieht obwohl es eigentlich sinnvoll wäre. Aber Programmierer
sind halt faul...
Vincent H. schrieb:> Du betrachtest die Dinge stets aus der Sicht der Funktion. Die ist in> der Praxis jedoch wesentlich unwichtiger als jene von außerhalb.
Ein ganz wichtiger Satz wurde einmal von Scott Meyers dazu gesagt:
"Eine Schnittstelle sollte leicht richtig, und nur schwer falsch zu
benutzen sein"
Diesen Satz sollte man sich bei jedem Schnittstellendesign in das
Gedächtnis rufen und beherzigen.
Vincent H. schrieb:> Das ist auch der Grund weshalb man das const keyword so selten bei pass> by value sieht obwohl es eigentlich sinnvoll wäre.
Ganz allgemein ist const-correctness eine super wichtige Sache.
Auch wenn const-correctness natürlich ein weites Feld ist, sollte man
auch hier etwa nach folgendem Spruch handeln:
"Mache alles(!) const was du kannst, und nur das non-const, was Du muss"
BTW: const in C++ heißt nicht im engeren Sinn "Konstant", sondern
read-only.
Daher auch der Hinweis, Parametervariablen in der Funktionsdefinition
auch bei pass-by_value const zu machen. Denn es sind Variablen, die nach
obiger Regel (meistens) eben read-only sind. Denn würde man ihren Wert
in der Funktion ändern, so kommst dies meist einem "Bedeutungswechsel"
gleich. Und so etwas macht den Code schwer verständlich. Kein Angst vor
zusätzlichen Kopien: vertraue dem Compiler, alles unnütze wird er
wegoptimieren.
Bspw ist es m.E. besser zu schreiben:
1
voidfoo(constintstartIndex){
2
...
3
autoindex=startIndex;
4
...
5
++index;
6
...
7
}
als
1
voidfoo(intstartIndex){
2
...
3
++startIndex;
4
...
5
}
denn hier hat die Variable startIndex später nicht mehr die Bedeutung
des Start-Indizes.
Michael schrieb:> Vincent H. schrieb:>> dann signalisierst du damit, dass der Übergabeparameter innerhalb der>> Funktion verändert wird. Im Gegensatz zur Kopie>> void SetAllPixel(bool);>> Ich dachte es ist genau anders herum, denn bei void SetAllPixel(bool);> muss ja eben erst eine Kopie erstellt werden...
In der Regel werden solche einfachen Parameter in Registern übergeben.
Konkret beim avr-g++ wird dann gar nichts kopiert. Wenn du stattdessen
aber eine (const-)Referenz übergibst, wird die Variable erst vom
Register in den Speicher kopiert, dann deren Adresse in das
Übergaberegister geschrieben und damit an die Funktion übergeben, so
dass diese den bool dann wieder aus dem Speicher in ein Register
zurückkopieren kann. Die Adresse, die an die Funktion übergeben wird,
ist zudem größer als der bool selbst.
Das ist also mehr Aufwand.
Rolf M. schrieb:> Michael schrieb:>> Vincent H. schrieb:>>> dann signalisierst du damit, dass der Übergabeparameter innerhalb der>>> Funktion verändert wird. Im Gegensatz zur Kopie>>> void SetAllPixel(bool);>>>> Ich dachte es ist genau anders herum, denn bei void SetAllPixel(bool);>> muss ja eben erst eine Kopie erstellt werden...>> In der Regel werden solche einfachen Parameter in Registern übergeben.> Konkret beim avr-g++ wird dann gar nichts kopiert. Wenn du stattdessen> aber eine (const-)Referenz übergibst, wird die Variable erst vom> Register in den Speicher kopiert, dann deren Adresse in das> Übergaberegister geschrieben und damit an die Funktion übergeben, so> dass diese den bool dann wieder aus dem Speicher in ein Register> zurückkopieren kann. Die Adresse, die an die Funktion übergeben wird,> ist zudem größer als der bool selbst.> Das ist also mehr Aufwand.
Oft kann der Compiler das aber optimieren.
Beim gcc ist diese Optimierung irgendwie immer nur bei
template-Funktionen aktiv. Dazu folgendes Beispiel: hier ist das
normalerweise stattfindende inlining man explizit abgeschaltet, damit
man mal sieht, was der Compiler generieren würde, wenn mal nicht
ge-inlined wird.
Man sieht, dass hier die isra-Optimierung gegriffen hat (alles, was
kleiner eines Pointer-Typs ist, wird direkt kopiert).
Ärgerlicherweise wird das wohl beim Testen des AVR-Backends vergessen zu
testen. Denn es schwankt immer nach Version des avr-g++, ob das
stattfindet oder nicht. Aktuell ist es gerade wieder aus dem gcc-10.0.0
draußen :-( Das obige wurde daher mit avr-g++ 9.2.0 erzeugt.
Umgekehrt werden aber auch kleine Objekte von UDTs, die per-value
übergeben werden, direkt in Registern transportiert.
Das war nur der Vollständigkeit halber. Die obige Regel (primitive DF
per-value, UDT per const-ref) bleibt erhalten.
Wilhelm M. schrieb:> Ärgerlicherweise wird das wohl beim Testen des AVR-Backends vergessen zu> testen. Denn es schwankt immer nach Version des avr-g++, ob das> stattfindet oder nicht. Aktuell ist es gerade wieder aus dem gcc-10.0.0> draußen :-( Das obige wurde daher mit avr-g++ 9.2.0 erzeugt.
Ich hatte es mit meinem avr-gcc mit einem non-template ausprobiert, und
der hat es nicht gemacht. Deshalb hab ich gedacht, dass das immer so
sei. Wenn man das ändert, heißt das ja auch jedes mal eine Änderung des
ABI, was ja eigentlich möglichst vermieden werden sollte.
Wilhelm M. schrieb:> Ärgerlicherweise wird das wohl beim Testen des AVR-Backends vergessen zu> testen. Denn es schwankt immer nach Version des avr-g++, ob das> stattfindet oder nicht. Aktuell ist es gerade wieder aus dem gcc-10.0.0> draußen :-( Das obige wurde daher mit avr-g++ 9.2.0 erzeugt.
Was führt dich zu dem Schluss, es sei ein Problem des Backends?
Rolf M. schrieb:> [...] heißt das ja auch jedes mal eine Änderung des ABI,
Wo siehst du da eine ABI-Änderung?
> was ja eigentlich möglichst vermieden werden sollte.
Johann L. schrieb:> Was führt dich zu dem Schluss, es sei ein Problem des Backends?
Ehrlich gesagt: ich habe keine Ahnung.
Ich meine mich lediglich zu erinnern, dass das für andere Zielsysteme
anders ist. Jedoch ist das schon eine Zeit her, und ich habe es
tatsächlich nur gerade nochmal für AVR und gcc-9.2 vesrus 10.0 geprüft.
Also: kläre uns bitte auf! Wo findet diese Art der Optimierung statt?
Johann L. schrieb:> Rolf M. schrieb:>> [...] heißt das ja auch jedes mal eine Änderung des ABI,>> Wo siehst du da eine ABI-Änderung?
Die Art, wie Parameter übergeben werden, ist Teil des ABI. Wenn ein
skalarer const-Referenzparameter in einer Version per Zeiger und in der
anderen direkt in einem Register übergeben wird, ist das
selbstverständlich eine Änderung des ABI.
Rolf M. schrieb:> Johann L. schrieb:>> Rolf M. schrieb:>>> [...] heißt das ja auch jedes mal eine Änderung des ABI,>>>> Wo siehst du da eine ABI-Änderung?>> Die Art, wie Parameter übergeben werden, ist Teil des ABI. Wenn ein> skalarer const-Referenzparameter in einer Version per Zeiger und in der> anderen direkt in einem Register übergeben wird, ist das> selbstverständlich eine Änderung des ABI.
Testfall?
Wilhelm M. schrieb:> Z1fIhEvRKT.isra.0:> ...> Z1fIjEvRKT.isra.0:
Die Parameterübergabe dieser beiden Funktionen ist per -fipa-sra
optimiert worden (deswegen das "isra" im Label), siehe
https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
Die Funktionen sind nur in der Übersetzungseinheit, in der sie definiert
wurden, bekannt und müssen sich deswegen an keinerlei ABI-Konventionen
halten.
Werden diese Funktionen in mehreren Übersetzungseinheiten verwendet,
wird in diesen jeweils eine eigene Kopie der Funktionen angelegt, was
bei größeren Funktionen ziemlich speicherfressend wäre. Deswegen wird ab
einer gewissen Größe der Funktionen auf die obige Optimierung verzichtet
und statt dessen von jeder Funktion nur eine einzige, ABI-konforme
Instanz angelegt (bzw. dafür gesorgt, dass mehrere gleiche Instanzen
beim Linken zu einer einzigen zusammengefasst werden). Diese bekommt
dann tatsächlich nicht eine Kopie, sondern wie erwartet eine Referenz
von a bzw. b übergeben.
Die Kriterien dafür, ob die Parameterübergabe per SRA optimiert wird
oder nicht, können natürlich von Version zu Version etwas variieren.
Damit ist aber keine Änderung des ABI verbunden.
Hallo,
vielleicht mal ein Hinweis für die Vorschnellen. GCC 10 ist noch lange
nicht fertig. Es wäre vermessen jetzt schon voreilige Rückschlüsse zu
ziehen.
Yalu X. schrieb:> Wilhelm M. schrieb:>> Z1fIhEvRKT.isra.0:>> ...>> Z1fIjEvRKT.isra.0:>> Die Parameterübergabe dieser beiden Funktionen ist per -fipa-sra> optimiert worden (deswegen das "isra" im Label), siehe>> https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html>> Die Funktionen sind nur in der Übersetzungseinheit, in der sie definiert> wurden, bekannt und müssen sich deswegen an keinerlei ABI-Konventionen> halten.
Tatsächlich halten sich diese Funktionen ans ABI :-)
Das ABI ändert sich durch Inter-Procedure Value Propagation genauso
wenig, wie es sich durch Inlining ändert. Konkret: ist der 1. Parameter
von xxx.isra.0 z.B. ein (impliziter) Pointer oder eine Referenz, so wird
diese in R25:R24 übergeben.
ABER: Das Interface von xxx.isra.0 ist ein anderes als das von xxx.
Sinn & Zweck von IPVP ist nämlich gerade, dass im Caller bekannte
Parameter wie true nicht an den Callee übergeben werden (müssen), d.h.
es wird eine andere, künstliche (artificial) Funktion erstellt, die
andere Parameter hat als die Originalfunktion xxx. Die
Parameterübergabe des Clones folgt jedoch 100% dem ABI.
Intern werden Clones zwar anders erzeugt, man könnte sich ein Clone aber
auch so erzeugt vorstellen: 1) Der Callee wird geinlinet 2) ein Stück
des Callers wird in eine separate Funktion geoutlined, wobei der
(Assembler-)Name der Outline-Funktion mit keinem Name (also mangled
Name) einer C- oder C++-Funktion kollidiert, weil in einem anderen
Namespace ("." im asm-Name).
Wilhelm M. schrieb:> Bspw ist es m.E. besser zu schreiben:> void foo(const int startIndex) {> ...> auto index = startIndex;
Das verkompliziert den Code nur unnötig. Da es in C (ich mag es auch
nicht) extra die Unterscheidung 'call by ...' gibt, kann man, oder
besser sollte jeder, es auch so nutzen. Basta!
sieht unschön aus und was interessiert den Caller ob in der
Implementierung param const ist.
b) In der Definition bringt ein const param IMHO keine Benefits, klar
kann man den dann überschreiben aber das ist für mich legitimer Code.
Helmie schrieb:> Das verkompliziert den Code nur unnötig.Guest schrieb:> Jo, sehe ich auch so.
Seid ihr sicher, dass ihr gelesen und verstanden habt, was Wilhelm M.
geschrieben hat?
Helmie schrieb:> Basta!
Vermutlich nicht.
Wie Willi schrieb, kümmert sich der Compiler um die Umsetzung. Also ist
für den Programmierer egal was unter der Gaube passiert. C ist ne
veraltete Sch... Sprache. Man muss es nicht unnötig kompliziert machen.
Für Willi und seine Spießgesellen ist es Selbstzweck. Der Weg ist Euer
Ziel. ?
Guest schrieb:> Eine Deklarationvoid foo(const int param); sieht unschön aus und was> interessiert den Caller ob in der> Implementierung param const ist.
Genau. Und deswegen hatte ich das auch NICHT(!) vorgeschlagen (s.o.).
Guest schrieb:> In der Definition bringt ein const param IMHO keine Benefits, klar> kann man den dann überschreiben aber das ist für mich legitimer Code.
Klar wäre das legitimer Code, genauso wie Code, der auf
const-correctness verzichtet (nicht zwischen Beobachtern und
Modifikatoren unterscheidet). Es ist aber schlechter Code.
Wenn man viel fremden Code liest / lesen muss, so ist man froh, wenn er
so expressiv wie möglich ist und seine Intention möglich schnell zu
erfassen ist. Und genau dazu trägt die const-Quali der
Parametervariablen bei.
Zudem kann die const-Quali Fehler verhindern, wie oben schon mal
geschrieben. Natürlich kann man z.B. triviale Fehler (s.o.) für
primitive DT auch per YODA-Style zu verhindern versuchen. Es bleiben
aber genug andere Fehlerquellen.
BTW: ich gehöre zu der Gruppe, die meint, ALLE Variablen sollten
standardmäßig read-only sein. Und die Modifizierbarkeit sollte
explizit(!) mit etwa dem Schlüsselwort mutable qualifiziert werden. Also
genau umgekehrt als in C/C++ jetzt. Ebenso wie alle ctor'en
standardmäßig explicit und alle Funktionen constexpr sein sollten.
Wilhelm M. schrieb:> Wenn man viel fremden Code liest / lesen muss, so ist man froh, wenn er> so expressiv wie möglich ist und seine Intention möglich schnell zu> erfassen ist. Und genau dazu trägt die const-Quali der> Parametervariablen bei.
Das Problem ist einfach, dass es sich beim Funktionskopf ja eigentlich
um die Definition einer Schnittstelle handelt. Das const steht da mitten
drin, ist aber eigentlich nicht Teil der Schnittstellendefinition. Das
macht es meiner Meinung nach unschön.
> BTW: ich gehöre zu der Gruppe, die meint, ALLE Variablen sollten> standardmäßig read-only sein.
Das würde ich auch besser finden. Gibt es eigentlich eine Sprache, die
das so handhabt?
Rolf M. schrieb:> Das Problem ist einfach, dass es sich beim Funktionskopf ja eigentlich> um die Definition einer Schnittstelle handelt.
Nun ja, zwar ist eine Definition auch eine Deklaration. Jedoch ist es ja
üblich, sinnvoll und meistens zwingend, Deklaration und Definition zu
trennen. Und dann verwendet man die const-Quali bei per-value nur in der
Definition, denn nur dort hat sie eine Bedeutung. In der Deklaration ist
sie überflüssig (und verwirrend).
Rolf M. schrieb:> Das würde ich auch besser finden. Gibt es eigentlich eine Sprache, die> das so handhabt?
Unter den funktionale Sprachen alle. Unter den Systemsprachen ging Rust
diesen Weg.
Wilhelm M. schrieb:> Und dann verwendet man die const-Quali bei per-value nur in der> Definition, denn nur dort hat sie eine Bedeutung. In der Deklaration ist> sie überflüssig (und verwirrend).
Verwirrend finde ich aber auch, wenn Deklaration und Definition
unterschiedlich aussehen.
Wilhelm M. schrieb:> BTW: ich gehöre zu der Gruppe, die meint, ALLE Variablen sollten> standardmäßig read-only sein.
Ich auch.
Vincent H. schrieb:> Rolf M. schrieb:>> Das würde ich auch besser finden. Gibt es eigentlich eine Sprache, die>> das so handhabt?>> Unter den funktionale Sprachen alle. Unter den Systemsprachen ging Rust> diesen Weg.
Das war das erste Feature, das ich an Rust interessant fand und das mich
(zusammen mit der Rust-eigenen Pointer-Philosophie) veranlasste, mir
diese Sprache etwas näher anzuschauen.
Mich wundert es etwas, dass das nicht in viel mehr imperativen Sprachen
so gehandhabt wird. Denn schließlich werden in den meisten Programmen
gefühlt sowieso schon mehr als die Hälfte aller Variablen nur einmal
beschrieben und danach nicht mehr verändert. Man würde damit den
Programmierern also kaum Steine in den Weg legen. Const als Default
würde die Programmierer dazu bewegen, nicht nur diese, sondern auch noch
weitere Variablen immutable zu machen, was dem Aufdecken von Fehlern
durch den Compiler und den Optimierungsmöglichkeiten entgegenkommt.
C++ ist eine alte Sprache, da lässt sich so etwas im Nachhinein kaum
noch ändern. Aber warum ging man bspw. bei C# nicht diesen Weg? Die
Sprachdesigner hatten damals alle Freiheiten (es bestand keinerlei
Kompatibilitätszwang), und die Diskussion über die Vorteile von const
gab es ja schon länger.
Rolf M. schrieb:> Verwirrend finde ich aber auch, wenn Deklaration und Definition> unterschiedlich aussehen.
Ja, logisch sinnvoller wäre es, die const-Deklaration von Parametern
nicht im Funktionskopf, sondern im Implementierungsteil vorzunehmen.
Nur müsste dann fast jeder Parameter ein weiteres Mal deklariert werden,
was auch wieder unschön ist.
Auch hier würde ein const-Default helfen.
Rolf M. schrieb:> Verwirrend finde ich aber auch, wenn Deklaration und Definition> unterschiedlich aussehen.
Finde ich eigentlich nicht: das eine ist die Schnittstelle, das andere
die Realisierung.
Außerdem ist das auch bei anderen Charakteristiken von Funktionen so:
z.B. default-Werte, non-/static und Attribute.
Yalu X. schrieb:> Aber warum ging man bspw. bei C# nicht diesen Weg?
Schau Dir Java an:
- Variablen r/w
- final nicht konsequent (s.a. immutables)
- Monitore (synchronized) nicht konsequent
- Generics nicht konsequent
- ...
Yalu X. schrieb:> Wilhelm M. schrieb:>> BTW: ich gehöre zu der Gruppe, die meint, ALLE Variablen sollten>> standardmäßig read-only sein.>> Ich auch.>> Vincent H. schrieb:>> Rolf M. schrieb:>>> Das würde ich auch besser finden. Gibt es eigentlich eine Sprache, die>>> das so handhabt?>>>> Unter den funktionale Sprachen alle. Unter den Systemsprachen ging Rust>> diesen Weg.>> Das war das erste Feature, das ich an Rust interessant fand und das mich> (zusammen mit der Rust-eigenen Pointer-Philosophie) veranlasste, mir> diese Sprache etwas näher anzuschauen.
Inzwischen gibt es ein Bounty für GCC+Rust:
https://www.bountysource.com/issues/86138921-rfe-add-a-frontend-for-the-rust-programming-language
Ist vermutlich erfolgreicher als das Bounty für avr-gcc...
Es gibt zwar bereits Rust-Frontends für GCC, die aber nicht Teil der GCC
sind.
http://gcc.gnu.org/ml/gcc-patches/2019-12/msg01568.html
Das Frontend muss also "nur" noch integriert werden.
Dass es irgendwann mal einen avr-grust geben wird, ist allerdings sehr
unwahrscheinlich. Zum einen fliegt AVR demnächst mit ziemlicher
Sicherheit aus GCC raus, zum anderen ist eine Sprache mehr als ihr Kern,
und ein Großteil ihrer Features wird per Bibliotheken verfügbar gemacht.
Hier würde sich wohl eine ähnliche Situation ergeben wie mit avr-g++:
Wird zwar verwendet, aber jeder bastelt sich seine eigene type_traits,
initializer_list etc., aber C++ auf AVR ist nicht so prickelnd, als dass
jemand richtigen C++ Support hinzufügen würde.
Yalu X. schrieb:> C++ ist eine alte Sprache, da lässt sich so etwas im Nachhinein kaum> noch ändern. Aber warum ging man bspw. bei C# nicht diesen Weg?
Wird const-Korrektheit dort überhaupt in dem Umfang wie in C++ gelebt?
Wilhelm M. schrieb:> Rolf M. schrieb:>> Verwirrend finde ich aber auch, wenn Deklaration und Definition>> unterschiedlich aussehen.>> Finde ich eigentlich nicht: das eine ist die Schnittstelle, das andere> die Realisierung.>> Außerdem ist das auch bei anderen Charakteristiken von Funktionen so:> z.B. default-Werte, non-/static und Attribute.
static schreibe ich nur bei Klassenmembern nicht dazu, weil es da (warum
auch immer) verboten ist. Das finde ich recht ärgerlich, da man dann an
der Definition nicht gleich erkennen kann, dass es sich um eine
statische Memberfunktion handelt.
Aber das hat weniger damit zu tun, dass es sich um die Definition der
Funktion handelt, sondern damit, dass es außerhalb der Klassendefinition
steht. Innerhalb der Klassendefinition ist es ja erlaubt. Also sowas
geht:
1
classFoo
2
{
3
public:
4
staticvoidfunc(){std::cout<<"Hello world\n";}
5
}
Bei Default-Werten halte ich es aber auch für nicht so sinnvoll, die zu
duplizieren und somit immer an zwei Stellen konsistent zu einander
halten zu müssen. Aber ja, da ist das ein Unterschied, der mich aber
irgendwie nicht so stört. Ich kann nicht so genau sagen, warum.
Und warum Attribute bei der Definiton verboten sind, ist mir ebenfalls
unverständlich. Gerade die sind für den Nutzer der Funktion in der Regel
weniger interessant. Und wie nervig das ist, sieht man ja auch an deinem
Code von oben. Da musstest du den ganzen Krempel extra zweimal
hinschreiben, nur weil das Attribut bei der Definition aus irgendeinem
Grund verboten ist:
Rolf M. schrieb:> static schreibe ich nur bei Klassenmembern nicht dazu, weil es da (warum> auch immer) verboten ist.
Naja, da bedeutet static ja auch was anderes?
Am besten ist es, die moderne, inline static in-class definition zu
verwenden. Dann steht es auch da, wo es hingehört.
Rolf M. schrieb:> Bei Default-Werten halte ich es aber auch für nicht so sinnvoll, die zu> duplizieren
Sollst Du auch gar nicht: die gehören ja nur in die Deklaration, sonst
gehts es ja nicht.
Rolf M. schrieb:> Und warum Attribute bei der Definiton verboten sind, ist mir ebenfalls> unverständlich.
Ist doch klar: weil das in den meisten Fällen Info für den Caller ist,
d.h. die Attribute müssen bei der Deklaration sichtbar sein, bei der
Definition ist es zu spät.
Wilhelm M. schrieb:> Rolf M. schrieb:>> Bei Default-Werten halte ich es aber auch für nicht so sinnvoll, die zu>> duplizieren>> Sollst Du auch gar nicht: die gehören ja nur in die Deklaration, sonst> gehts es ja nicht.
Das hat nichts damit zu tun, ob es eine Definition ist. Es ist an der
Stelle erlaubt, an der die Funktion zum ersten mal bekannt gemacht wird,
egal ob das eine Definition ist oder nicht.
Also:
1
voidfunc(inti=3)// erlaubt
2
{
3
4
}
5
6
voidfunc(inti);// erlaubt, default-Wert ist 3
7
voidfunc(inti=3);// Fehler, weil default-Wert schon vorher festgelegt
8
// Warum kann das nicht erlaubt sein, solange der Wert gleich ist?
> Rolf M. schrieb:>> Und warum Attribute bei der Definiton verboten sind, ist mir ebenfalls>> unverständlich.>> Ist doch klar: weil das in den meisten Fällen Info für den Caller ist,> d.h. die Attribute müssen bei der Deklaration sichtbar sein, bei der> Definition ist es zu spät.
Ok, für manche Attribute gilt das. Aber an sich würde es auch wie oben
bei den Default-Werten reichen, wenn das Attribut für die erste Stelle,
an der die Funktion bekannt gemacht wird, erlaubt wäre. Dann wäre bei
deinem obigen Beispiel die Duplizierung des Funktionskopfs nur für das
Attribut eben nicht nötig.
Und es würde auch nicht schaden, wenn es an allen Stellen erlaubt wäre,
solange die Attribute überall die gleichen sind.
Rolf M. schrieb:> Das hat nichts damit zu tun, ob es eine Definition ist. Es ist an der> Stelle erlaubt, an der die Funktion zum ersten mal bekannt gemacht wird,
Und das ist typischerweise die Deklaration (beachte, eine Definition ist
auch eine Deklaration).