Ich verstehe den Zweck dahinter (Sichtbarkeit von Variablen
beschraenken), aber ich finde, dass es die Lesbarkeit doch sehr
verschlechtert. Gerade bei groesseren Projekten kann ich mir vorstellen,
dass das bei einem Codereview eher stoert, da man jetzt jedes if noch
genauer unter die Lupe nehmen muss.
Natuerlich, man muss das Feature ja nicht benutzen, schon klar.
Wie ist eure Meinung dazu?
Gruesse
Ich finde es super. Vor Allem weil man die temporären Variablen, die man
im init Statement erzeugt, meistens auch nur im if-context benötigt und
sie sonst außerhalb deklarieren müsste. Insofern räumt es also sogar das
Scope außerhalb des ifs auf, weil dort eine Variable weniger rumhängt.
Beim Code Review sollte es auch nicht weiter stören, weil die Variable
ja dann auch nur im if-context existiert.
Das ist das gleiche 'Problem', wie bei Schleifen. Immer wenn ich C
programmieren muss, vermisse ich die Deklaration von Variablen in
Schleifenköpfen.
Bei If bin ich es noch nicht gewohnt, deshalb vermisse ich es auch
nicht, aber wenn es sich einmal verbreitet hat, wird das auch nicht
anders sein, als bei Schleifen.
Dussel schrieb:> Immer wenn ich C programmieren muss, vermisse ich die Deklaration von> Variablen in Schleifenköpfen.
Dann nimm doch einen halbwegs aktuellen Compiler …
man kann auch Variablen in einen {} Block setzen, damit hat man auch den
Gültigkeitsbereich eingegrenzt. So mache ich das gerne, das entschärft
etwas Kopierfehler bei denen eine Variable dann evtl. nicht mehr den
richtigen init Wert hat wenn sie wiederverwendet wird. Und das geht noch
mit altmodischem C++98.
Hallo,
also ich finde die If statement with initializer Variante viel lesbarer,
weil ja sofort und eindeutig geklärt ist, dass die Variable nur für den
if-scope gedacht ist. Wenn ich die Variable im umgebenden scope
definieren muss, muss der Leser ja erstmal untersuchen, ob sie noch
irgendwo gebraucht wird.
Zusammen mit structured bindings ergibt das wirklich lokale und präzise
Ausdrücke und er der Code wird viel aussagekräftiger.
Meine Meinung
vlg
Timm
Kaj G. schrieb:> Hallo Leute,>> mich wuerde einfach mal eure Meinung zu diesem Feature interessieren.>> C++17 If statement with initializer> https://skebanga.github.io/if-with-initializer/> if (init; condition)> Ich verstehe den Zweck dahinter (Sichtbarkeit von Variablen> beschraenken), aber ich finde, dass es die Lesbarkeit doch sehr> verschlechtert. Gerade bei groesseren Projekten kann ich mir vorstellen,> dass das bei einem Codereview eher stoert, da man jetzt jedes if noch> genauer unter die Lupe nehmen muss.>> Natuerlich, man muss das Feature ja nicht benutzen, schon klar.>> Wie ist eure Meinung dazu?
Du hast doch schon eigentlich einen ganz guten Post dazu zitiert.
Überzeugt Dich das nicht?
Gerade im Zusammenhang mit den Iteratoren / Algorithmen der stdlibc++
ist es sehr interessant. Weil es einfach den Code kürzer macht und
oftmals ein explicit-bool-conv-op obsolet macht.
Nur eine gelöschte Codezeile ist eine gute Codezeile.
Dussel schrieb:> Immer wenn ich C programmieren muss, vermisse> ich die Deklaration von Variablen in Schleifenköpfen.
Dussel schrieb explizit *köpfen*:
1
for(inti=0;i<MAX_STUFF;i++)
2
{
3
doStuff(i);
4
}
5
/* und hier sollte i wieder unbekannt sein!! */
Das Problem in C ist doch, dass i unten weiterlebt.
Ich finde das Feature einfach nur unnötig. Es gibt bereits einfache
Mittel um den Scope von Variablen zu begrenzen und es verlagert den Code
ja nur von 2 Zeilen auf eine Zeile. Ich finde alles auf eine Zeile zu
packen reduziert nur die Lesbarkeit.
Achim S. schrieb:> Dussel schrieb explizit *köpfen*:> for(int i=0; i < MAX_STUFF; i++)> {> doStuff(i);> }> /* und hier sollte i wieder unbekannt sein!! */>> Das Problem in C ist doch, dass i unten weiterlebt.
Nein, das tut i nicht.
Wenn man sich die Kommentare durchliest, könnte man auf den Gedanken
kommen, dass c++17 die Definition von Variablen in der if-condition
einführt. Neu ist allerdings nur die explizite Trennung von init und
condition.
>> Das Problem in C ist doch, dass i unten weiterlebt.
s:C:JavaScript:
In JavaScript werden Deklarationen zum Anfang des Gültigkeitsbereich
gehoistet, d.h.
Achim S. schrieb:> Das Problem in C ist doch, dass i unten weiterlebt.
Nein. Dieses Verhalten kenne ich nur von Visual Studio 2003, und da ist
das ein bekannter Bug.
Johann L. schrieb:> In JavaScript werden Deklarationen zum Anfang des Gültigkeitsbereich> gehoistet, d.h.
Deswegen sollte man ES2015 und let/const statt var verwenden. ;)
Jörg W. schrieb:> Dussel schrieb:>> Immer wenn ich C programmieren muss, vermisse ich die Deklaration von>> Variablen in Schleifenköpfen.>> Dann nimm doch einen halbwegs aktuellen Compiler …
Ok, ich meinte was anderes. Ich vermisse es, wenn ich C auf einem System
programmieren muss, auf dem das nicht geht.
mh schrieb:> Wenn man sich die Kommentare durchliest, könnte man auf den Gedanken> kommen, dass c++17 die Definition von Variablen in der if-condition> einführt. Neu ist allerdings nur die explizite Trennung von init und> condition.> if(const char* hallo = "Welt") {}> ist schon in c++98 möglich.
Das wusste ich tatsächlich nicht.
Daniel A. schrieb:> Ich finde das Feature einfach nur unnötig. Es gibt bereits einfache> Mittel um den Scope von Variablen zu begrenzen und es verlagert den Code> ja nur von 2 Zeilen auf eine Zeile. Ich finde alles auf eine Zeile zu> packen reduziert nur die Lesbarkeit.
Du definierst nie eine Variable im Schleifenkopf? Das ist mir auch noch
nicht untergekommen.
Das ganze ist doch eine herrliche Vereinfachung einfach deswegen, weil
nun for/if/switch nach demselben Prinzip gebildet werden können. Und
m.E. ist jede Art von Gleichförmigkeit gut, weil man sich weniger merken
muss ...
Wie ist das eigentlich mit dem Stackverbrauch bei Variablen-Scoping? Ist
natürlich implementationsabhängig, aber was ist da üblich?
1) der Compiler ist schlau genug, disjunkte Scopes zu erkennen und
verwendet dieselben Stack-Slots wieder
2) der Compiler kriegt das nicht hin und verbraucht die Summe aller
Variablen
Im Falle von 2 würde konsequentes Scoping dann ja zu deutlich höherem
Stackverbrauch führen.
Der Scope einer Variablen ist doch nur der theoretisch längstmögliche
Zeitraum, in dem diese Variable „lebt“.
Alle aktuellen Compiler limitieren die tatsächliche Lebensdauer auf
das notwendige Minimum. Es ist ja nicht nur Stack, viel knapper ist
im Allgemeinen die Ressource „Register“, sodass man diese natürlich
maximal wiederverwenden können möchte. Sowie eine Variable nicht mehr
gebraucht wird, fliegt sie da wieder raus. Variablen, die gar nicht
gebraucht werden, bekommen folglich dann auch nie reale Ressourcen
zugewiesen.
Das setzt natürlich eingeschaltete Optimierungen voraus.
Aber soweit ich weiß, wird die Größe des Stackframes nicht dynamisch
während der Laufzeit einer Funktion verändert, sprich: Aller benötigter
Stack für lokale Variablen wird zu Beginn der Funktion belegt, auch wenn
die Variablen erst später definiert werden. Oder ist das falsch? Bei
Registern mag das natürlich anders sein.
Jörg W. schrieb:> Dussel schrieb:>> Immer wenn ich C programmieren muss, vermisse ich die Deklaration von>> Variablen in Schleifenköpfen.>> Dann nimm doch einen halbwegs aktuellen Compiler …
Wobei "halbwegs aktuell" bedeutet, dass er nicht mehr auf dem Stand aus
den Neunzigern des letzten Jahrhunderts sein sollte.
Johannes S. schrieb:> man kann auch Variablen in einen {} Block setzen, damit hat man auch den> Gültigkeitsbereich eingegrenzt.
Das mache ich auch ganz gerne. Meist wird aber aus diesem Block später
dann eine eigene Funktion.
Cyberpunk schrieb:> Ich hätte ja lieber if-statements als Ausdruck wie z.B. bei Rust, aber> dass neue If ist auch nicht schlecht.
Das gibt es doch schon mit dem ?:-Operator.
Jörg W. schrieb:> Alle aktuellen Compiler limitieren die tatsächliche Lebensdauer auf> das notwendige Minimum.
sobald die Variablen Objekte sind, wird der Destruktor am ende vom Block
aufgerufen, so lange existiert sie. Da kann nichts optimiert werden.
Peter II schrieb:> Jörg W. schrieb:>> Alle aktuellen Compiler limitieren die tatsächliche Lebensdauer auf>> das notwendige Minimum.>> sobald die Variablen Objekte sind, wird der Destruktor am ende vom Block> aufgerufen, so lange existiert sie. Da kann nichts optimiert werden.
Selbstverständlich kann da optimiert werden. Solange alle Zugriffe auf
volatile-Variablen und alle File-I/O noch stattfinden wie im Code
angegeben, darf der Compiler da beliebig verändern (siehe "as-if rule").
Aber es ging gar nicht darum, ob der Destruktor verfrüht aufgerufen
wird, sondern darum, ob der Speicher länger als nötig belegt wird (also
z.B. schon bei Beginn der Funktion, obwohl das Objekt erst weiter unten
im Code definiert ist).
Rolf M. schrieb:> Aber soweit ich weiß, wird die Größe des Stackframes nicht dynamisch> während der Laufzeit einer Funktion verändert, sprich: Aller benötigter> Stack für lokale Variablen wird zu Beginn der Funktion belegt, auch wenn> die Variablen erst später definiert werden.
Das dürfte bei den meisten Compilern so sein.
Allerdings kann der Compiler natürlich auch auf dem Stack Variablen
überlagern, d. h. die gleiche Stackadresse kann zu verschiedenen
Zeiten für verschiedene Variablen genutzt werden.
Felix U. schrieb:> Ich finde es super. Vor Allem weil man die temporären Variablen, die man> im init Statement erzeugt, meistens auch nur im if-context benötigt
Witzbold, man KANN sie gar nicht ausserhalb verwenden.
Die Frage ist, wie oft man Variablen braucht, die bei der Auswertung des
IFs errechnet werden und dann nur im THEN oder ELSE Fall irgendwie
benötigt werden.
Eher selten.
Die Frage wäre auch, was an dem 'modernen' if
Michael B. schrieb:> Witzbold, man KANN sie gar nicht ausserhalb verwenden.
Im 'if'- context !
Dein 'i' wird auch (möglicherweise!) nur im Zusammenhang mit der
Abfrage benötigt.
Michael B. schrieb:> Die Frage wäre auch, was an dem 'modernen' if besser...
Beispiel (okay, Moeglicherweise ein nicht sonderlich gutes):
1
#include<new>
2
3
intmain()
4
{
5
if(int*ptr=new(std::nothrow)int;ptr!=nullptr){
6
*ptr=42;
7
deleteptr;
8
}
9
10
*ptr=666;// fehler!
11
12
return0;
13
}
Es gibt keine Moeglichkeit 'ausversehen' ausserhalb des if auf den schon
freigegebenen Speicher zuzugreifen (use-after-free). Also eine
Moeglichkeit, mit wenig Aufwand und wenig nachdenken, kritische
Sicherheitsluecken zu vermeiden. Ganz ohne das man da extra {} setzen
muss. Natuerlich kann man innerhalb des if nach dem delete immer noch
mist machen, aber es ist auf diesen (im idealfall sehr kleinen) Scope
begrenzt.
Bei deiner Methode besteht diese Gefahr aber.
1
#include<new>
2
3
intmain()
4
{
5
int*ptr=new(std::nothrow)int;
6
if(ptr!=nullptr){
7
*ptr=42;
8
}
9
10
deleteptr;
11
*ptr=666;// use-after-free
12
13
return0;
14
}
Und use-after-free ist ein recht haeufiger Fehler.
Und ein
1
#include<new>
2
3
intmain()
4
{
5
{
6
int*ptr=new(std::nothrow)int;
7
if(ptr!=nullptr){
8
*ptr=42;
9
}
10
11
deleteptr;
12
}
13
*ptr=666;
14
15
return0;
16
}
ist auch nicht wirklich lesbarer, da es wieder eine Einrueckungstiefe
einfuegt, die man mit diesem Feature vermeiden kann. Aber das ist wieder
Ansichtssache.
Jörg W. schrieb:> Allerdings kann der Compiler natürlich auch auf dem Stack Variablen> überlagern, d. h. die gleiche Stackadresse kann zu verschiedenen> Zeiten für verschiedene Variablen genutzt werden.
Genau, aber tut z.B. ARM-GCC das auch tatsächlich?
Nop schrieb:> Jörg W. schrieb:>>> Allerdings kann der Compiler natürlich auch auf dem Stack Variablen>> überlagern, d. h. die gleiche Stackadresse kann zu verschiedenen>> Zeiten für verschiedene Variablen genutzt werden.>> Genau, aber tut z.B. ARM-GCC das auch tatsächlich?
Ja.
Michael B. schrieb:> Die Frage ist, wie oft man Variablen braucht, die bei der Auswertung des> IFs errechnet werden und dann nur im THEN oder ELSE Fall irgendwie> benötigt werden.>> Eher selten.
Bei mir kommt es ziemlich häufig vor, dass ich Variablen nur im
statement-true und/oder statement-false benötige. Z.B. im zusammenhang
mit map, set und co.
1
voidmap_bsp(std::map<int,int>&m){
2
if(autoi=m.find(42);i!=m.end()){
3
4
}
5
else{
6
7
}
8
}
9
10
voidset_bsp(std::set<int>&s,intv){
11
if(auto[i,flag]=s.insert(v);flag){
12
std::cout<<*i<<std::endl;
13
}
14
else{
15
16
}
17
}
> Die Frage wäre auch, was an dem 'modernen' ifif(int i;(i=anzahl())>0)> {> cout << "Anzahl: " << i;> }> oderif(int i=anzahl();i>0)> {> cout << "Anzahl: " << i;> }
Warum sollte man die erste Variante benutzen?
> besser und verständlicher ist als an{> int i;> if((i=anzahl())>0)> {> cout << "Anzahl: " << i;> }> }> oder wie ich finde übersichtlicher{> int i;> i=anzahl();> if(i>0)> {> cout << "Anzahl: " << i;> }> }
Warum sollte man die erste Variante benutzen?
Und ja, ich finde
1
if(inti=anzahl();i>0){}
übersichtlicher, als deine bevorzugte Varianten. Vor allem mit deinem
mh schrieb:> Vor allem mit deinem> int i;> i = anzahl();> ...> habe ich Probleme.
Ja, so ein Code kann wirklich ziemlich schnell unübersichtlich werden.
Vor allem wenn ich mehrere Variablen gleichzeitig definiere. Da kann man
leicht mal was vergessen. Außerdem kann man auto nicht benutzen, was
gerade bei Datentypen mit langen Namen (bzw. Templates) viel Aufwand
spart.
Michael B. schrieb:> Die Frage ist, wie oft man Variablen braucht, die bei der Auswertung des> IFs errechnet werden und dann nur im THEN oder ELSE Fall irgendwie> benötigt werden.>> Eher selten.
Ich brauche das eigentlich ständig. Ich würde vielmehr sagen, dass ich
eher selten die Variable danach noch brauche.
jz23 schrieb:> Ja, so ein Code kann wirklich ziemlich schnell unübersichtlich werden.> Vor allem wenn ich mehrere Variablen gleichzeitig definiere. Da kann man> leicht mal was vergessen. Außerdem kann man auto nicht benutzen, was> gerade bei Datentypen mit langen Namen (bzw. Templates) viel Aufwand> spart.
Ich bevorzuge es auch, Variablen gleich bei der Definition mit ihrem
Wert vorzubelegen, wenn es geht. Ich finde jedenfalls ein
1
inti;
2
i=3;
weder lesbarer, noch eleganter als ein
1
inti=3;
Und bei Klassen kommt dann natürlich noch dazu, dass die Erzeugung ggf
umständlicher ist, wenn erst der Default-Konstruktor das Objekt erzeugt
und dann eine Zuweisung kommt, als wenn es gleich mit dem richtigen
Konstruktor erzeugt wird.
Ganz nebenbei kann man mit dieser Syntax elegant ein if-Statement inkl.
dem darin befindlichen Block atomar ausführen. Wenn im if-initializer
ein Objekt erzeugt wird, dessen Konstruktor z.B. die Falgs sichert und
INT's sperrt, und dessen Destruktor am Ende des "if-Blocks" die Flags
wieder "restored". Damit ist alles inkl. der "if-Condition" selbst
atomar (singlecore-AVR vorausgesetzt). Es reicht ein:
1
if(Guardguard;<Condition>){
2
// nö INT's!
3
}
Das kann man natürlich auch als separaten Block um den "if-Block"
schreiben, aber irgendwas in dieser Art mit automatischer Destruktion
bei Scope-Ende scheint oft genug gebraucht zu werden, daß das
ISO-Komitee dies aufnahm. Symmetrie zu "for" spricht ja aber auch nicht
dagegen.
Automatische Destruktion von Objekten ist der Hauptpunkt, weniger die
Bereitstellung von n Instanzen der Zähl-Variablen i innerhalb einer
Funktion.
Obiges Guard-Objekt belegt übrigens genau ein Byte, eine Kopie des
Flag-Registers, und landet, entsprechend kurzen "if-Block" vorausgesetzt
einfach in einen freien CPU-Register.