Warum muss ich das Schlüsselwort 'auto' explizit schreiben?

80

Ich wechsle von C ++ 98 zu C ++ 11 und habe mich mit dem autoSchlüsselwort vertraut gemacht . Ich habe mich gefragt, warum wir explizit deklarieren müssen, autoob der Compiler den Typ automatisch ableiten kann. Ich weiß, dass C ++ eine stark typisierte Sprache ist und dies eine Regel ist. War es jedoch nicht möglich, dasselbe Ergebnis zu erzielen, ohne eine Variable explizit zu deklarieren auto?

Farsan Rashid
quelle
Denken Sie daran, dass bei der C-Familie zwischen Groß- und Kleinschreibung unterschieden wird. Debuggen Sie einen JS-Code, bei dem der Autor "var" weggelassen hat, und verwenden Sie eine separate Variable mit Namen wie "Bob", "bob" und "boB". Pfui.
PTwr
46
Selbst wenn es möglich wäre, wäre es eine mäßig schreckliche Idee. Die wohl größte Schwäche von Python (und ähnlichen Sprachen) ist das Fehlen einer Deklarationssyntax. Es ist eine Hauptursache für Fehler, dass durch einfache Zuweisung eine neue Variable erstellt wird.
Konrad Rudolph
@KonradRudolph: JS ist mit seiner Deklarationssyntax allerdings nicht besser. Ich denke, sie wollten damit sagen, dass es die Unfähigkeit ist, den Umfang einer Variablen feinkörnig einzuschränken.
user541686
4
@Mehrdad "ändert die Semantik" ≠ "obligatorisch". Das Problem ist , dass JavaScript tut implizite Erklärungen akzeptieren. Ja, sie sind semantisch unterschiedlich, aber das hilft nicht im geringsten.
Konrad Rudolph
1
Siehe auch die doppelte Frage "Warum verwenden echte Perl-Benutzer" mein "Schlüsselwort stackoverflow.com/questions/8023959/why-use-strict-and-warnings/…
Yann TM

Antworten:

156

Das Löschen des Expliziten autowürde die Sprache beschädigen :

z.B

int main()
{
    int n;
    {
        auto n = 0; // this shadows the outer n.
    }
}

wo Sie sehen können, dass das Fallenlassen das Äußere autonicht beschatten würde n.

Bathseba
quelle
8
Tippte genau das Gleiche. Die Unterscheidung zwischen Zuordnung und Initialisierung würde eine willkürliche Auswahl seitens des Standards erfordern. Da wir bereits die Regel haben, dass "alles, was eine Erklärung sein könnte, eine Erklärung ist", treten wir in sehr trübes Wasser.
Geschichtenerzähler - Unslander Monica
4
Das ist kein Problem. Wie bei Golang können Sie natürlich so etwas wie n := 0eine neue Variable einführen. Warum verwendet autowird, ist eine meinungsbasierte Frage.
llllllllll
23
@liliscent - Ist es meinungsbasiert? (a) Es war bereits ein reserviertes Schlüsselwort. (b) Die Bedeutung ist ziemlich klar. (c) Es wird vermieden, dass neue Token (wie :=) eingeführt werden müssen. (d) Es passt bereits in die Grammatik. Ich denke, hier gibt es sehr wenig Raum für Meinungen.
Geschichtenerzähler - Unslander Monica
2
@StoryTeller Sie benötigen keine neuen Token, wenn Sie x = f()eine neue Variable deklarieren (falls noch nicht vorhanden), um den Typ des Rückgabewerts von f zu erhalten. Wenn Sie automatisch eine Variable explizit deklarieren, verringert sich jedoch das Risiko, neue Variablen zu deklarieren aus Versehen (zB wegen eines Tippfehlers ...).
Aconcagua
34
@Aconcagua - "deklariert eine neue Variable (falls noch nicht vorhanden)" Aber das Abschatten ist Teil der Sprache und muss immer noch funktionieren, wie Bathsheba zeigt. Es ist ein größeres Problem als man sich vorstellt. Es geht nicht darum, die Sprache von Grund auf neu zu gestalten, sondern eine lebendige Atemsprache zu ändern. Viel schwieriger zu tun. Ein bisschen wie das Rad in einem schnell fahrenden Auto zu wechseln.
Geschichtenerzähler - Unslander Monica
40

Ihre Frage erlaubt zwei Interpretationen:

  • Warum brauchen wir überhaupt "Auto"? Können wir es nicht einfach fallen lassen?
  • Warum müssen wir auto verwenden? Können wir es nicht einfach implizit haben, wenn es nicht gegeben ist?

Bathseba antwortete gut auf die erste Interpretation, für die zweite auf Folgendes (vorausgesetzt, es existieren noch keine anderen Erklärungen; hypothetisch gültiges C ++):

int f();
double g();

n = f(); // declares a new variable, type is int;
d = g(); // another new variable, type is double

if(n == d)
{
    n = 7; // reassigns n
    auto d = 2.0; // new d, shadowing the outer one
}

Es wäre möglich, dass andere Sprachen recht gut zurechtkommen (abgesehen von dem Shadowing-Problem vielleicht) ... In C ++ ist dies jedoch nicht der Fall, und die Frage (im Sinne der zweiten Interpretation) lautet nun: Warum ?

Diesmal ist die Antwort nicht so offensichtlich wie in der ersten Interpretation. Eines ist jedoch offensichtlich: Die explizite Anforderung des Schlüsselworts macht die Sprache sicherer (ich weiß nicht, ob dies das Sprachkomitee zu seiner Entscheidung geführt hat, dennoch bleibt es ein Punkt):

grummel = f();

// ...

if(true)
{
    brummel = f();
  //^ uh, oh, a typo...
}

Können wir uns darauf einigen, dass wir keine weiteren Erklärungen benötigen?

Die noch größere Gefahr, kein Auto zu benötigen, besteht jedoch darin, dass das Hinzufügen einer globalen Variablen an einer Stelle, die weit von einer Funktion entfernt ist (z. B. in einer Header-Datei), die Deklaration einer lokalen Datei ändern kann. Umfang Variable in dieser Funktion in eine Zuordnung zur globalen Variablen ... mit möglicherweise katastrophalen (und sicherlich sehr verwirrenden) Folgen.

(Zitierter Kommentar von psmears wegen seiner Wichtigkeit - danke für den Hinweis auf)

Aconcagua
quelle
24
Die noch größere Gefahr besteht automeiner Ansicht nach darin, dass das Hinzufügen einer globalen Variablen an einer Stelle, die weit von einer Funktion entfernt ist (z. B. in einer Header-Datei), die Deklaration eines lokal begrenzten Bereichs ändern kann Variable in dieser Funktion in eine Zuordnung zur globalen Variablen ... mit möglicherweise katastrophalen (und sicherlich sehr verwirrenden) Folgen.
psmears
1
@psmears Sprachen wie Python vermeiden dies, indem sie eine explizite Angabe einer Variablen als global / nicht lokal für Zuweisungen erfordern. Standardmäßig wird einfach eine neue lokale Variable mit diesem Namen erstellt. (Natürlich können Sie aus einer globalen Variablen lesen, ohne die global <variable>Anweisung zu benötigen .) Dies würde natürlich noch mehr Änderungen an der C ++ - Sprache erfordern und wäre daher wahrscheinlich nicht möglich.
JAB
@JAB - yep, ich bin mir dessen bewusst ... Ich habe es nicht erwähnt, weil es, wie Sie sagen, noch mehr Änderungen an der Sprache erfordern würde :)
psmears
FWIW, was das Sprachkomitee angetrieben hat, ist höchstwahrscheinlich Geschichte. AFAIK, als C ursprünglich geschrieben wurde, wurden lokale Variablen auf dem Stapel gespeichert, und C erforderte, dass alle Variablen zuerst in einem Block explizit deklariert wurden. Dadurch konnte der Compiler den Speicherbedarf für diesen Block ermitteln, bevor er den Rest des Codes kompilierte, und die richtige Befehlssequenz ausgeben, um Speicherplatz auf dem Stapel zuzuweisen. IIRC MOV R6 R5 SUB #nnn R6auf einem PDP-11 unter der Annahme, dass R5 als Rahmenzeiger und R6 als Stapelzeiger verwendet wird. nnn ist die Anzahl der benötigten Speicherbytes.
Dgnuff
2
Es gelingt den Leuten, Python zu verwenden, das jedes Mal, wenn ein neuer Name auf der linken Seite einer Zuweisung erscheint, eine Variable deklariert (auch wenn dieser Name ein Tippfehler ist). Aber ich halte es für einen der schwerwiegendsten Fehler der Sprache.
Hobbs
15

War es nicht möglich, dasselbe Ergebnis zu erzielen, ohne eine Variable explizit zu deklarieren auto?

Ich werde Ihre Frage leicht umformulieren, damit Sie verstehen, warum Sie Folgendes benötigen auto:

War es nicht möglich, dasselbe Ergebnis zu erzielen, ohne explizit einen Typplatzhalter zu verwenden ?

War es nicht möglich ? Natürlich war es "möglich". Die Frage ist, ob sich die Mühe lohnt, dies zu tun.

Die meisten Syntaxen in anderen Sprachen, die keine Typnamen haben, funktionieren auf zwei Arten. Es gibt den Go-ähnlichen Weg, bei dem name := value;eine Variable deklariert wird. Und es gibt die Python-ähnliche Methode, bei der name = value;eine neue Variable deklariert wird, wenn namesie zuvor nicht deklariert wurde.

Nehmen wir an, dass es keine syntaktischen Probleme beim Anwenden einer der beiden Syntax auf C ++ gibt (obwohl ich bereits sehen kann, dass in C ++ identifiergefolgt von :"make a label" bedeutet). Was verlieren Sie also im Vergleich zu Platzhaltern?

Nun, ich kann das nicht mehr tun:

auto &name = get<0>(some_tuple);

Siehe, autobedeutet immer "Wert". Wenn Sie eine Referenz erhalten möchten, müssen Sie explizit a verwenden &. Und es wird zu Recht nicht kompiliert, wenn der Zuweisungsausdruck ein Wert ist. Keine der zuweisungsbasierten Syntaxen kann zwischen Referenzen und Werten unterscheiden.

Sie können jetzt festlegen, dass solche Zuweisungssyntaxen Referenzen ableiten, wenn der angegebene Wert eine Referenz ist. Aber das würde bedeuten, dass Sie nicht tun können:

auto name = get<0>(some_tuple);

Dies kopiert aus dem Tupel und erstellt ein Objekt unabhängig von some_tuple. Manchmal ist es genau das, was Sie wollen. Dies ist umso nützlicher, wenn Sie sich vom Tupel mit entfernen möchten auto name = get<0>(std::move(some_tuple));.

OK, vielleicht könnten wir diese Syntax ein wenig erweitern, um diese Unterscheidung zu berücksichtigen. Vielleicht &name := value;oder &name = value;würde bedeuten, eine Referenz wie abzuleiten auto&.

OK, gut. Was ist damit:

decltype(auto) name = some_thing();

Oh, das stimmt; C ++ hat tatsächlich zwei Platzhalter: autounddecltype(auto) . Die Grundidee dieses Abzugs ist, dass es genau so funktioniert, als ob Sie es getan hätten decltype(expr) name = expr;. Wenn some_thing()es sich in unserem Fall um ein Objekt handelt, wird daraus ein Objekt abgeleitet. Wenn some_thing()es sich um eine Referenz handelt, wird eine Referenz abgeleitet.

Dies ist sehr nützlich, wenn Sie im Vorlagencode arbeiten und nicht genau wissen, wie hoch der Rückgabewert einer Funktion sein wird. Dies ist ideal für die Weiterleitung und ein wesentliches Werkzeug, auch wenn es nicht weit verbreitet ist.

Jetzt müssen wir unsere Syntax erweitern. name ::= value;bedeutet "mach was decltype(auto)macht". Ich habe kein Äquivalent für die Pythonic-Variante.

Ist es bei dieser Syntax nicht einfach, versehentlich falsch zu tippen? Nicht nur das, es ist kaum selbstdokumentierend. Selbst wenn Sie es noch nie gesehen decltype(auto)haben, ist es groß und offensichtlich genug, dass Sie zumindest leicht erkennen können, dass etwas Besonderes los ist. Während der visuelle Unterschied zwischen ::=und :=minimal ist.

Aber das ist Meinungsmaterial; Es gibt wesentlichere Fragen. All dies basiert auf der Verwendung der Zuweisungssyntax. Nun ... was ist mit Orten, an denen Sie die Zuweisungssyntax nicht verwenden können? So was:

for(auto &x : container)

Ändern wir das in for(&x := container)? Denn das scheint etwas zu sagen , sehr verschieden von bereichsbasierten for. Es sieht so aus, als wäre es die Initialisiereranweisung einer regulären forSchleife, keine bereichsbasierte for. Es wäre auch eine andere Syntax als bei nicht abgeleiteten Fällen.

Außerdem ist die Kopierinitialisierung (mit =) in C ++ nicht dasselbe wie die Direktinitialisierung (mit Konstruktorsyntax). Funktioniert also name := value;möglicherweise nicht in Fällen, in denen auto name(value)dies der Fall wäre.

Sicher, Sie könnten deklarieren, dass :=die Direktinitialisierung verwendet wird, aber das wäre ziemlich inkongruent mit dem Verhalten von C ++.

Außerdem gibt es noch eine Sache: C ++ 14. Es gab uns eine nützliche Abzugsfunktion: den Abzug vom Rückgabetyp. Dies basiert jedoch auf Platzhaltern. forÄhnlich wie bei einem Bereich basiert es im Wesentlichen auf einem Typnamen, der vom Compiler ausgefüllt wird, und nicht auf einer Syntax, die auf einen bestimmten Namen und Ausdruck angewendet wird.

Alle diese Probleme stammen aus derselben Quelle: Sie erfinden eine völlig neue Syntax zum Deklarieren von Variablen. Platzhalterbasierte Deklarationen mussten keine neue Syntax erfinden . Sie verwenden genau die gleiche Syntax wie zuvor. Sie verwenden lediglich ein neues Schlüsselwort, das sich wie ein Typ verhält, aber eine besondere Bedeutung hat. Dies ermöglicht es ihm, im bereichsbezogenen forund für den Rückzugsartabzug zu arbeiten. Es ist das, was es erlaubt, mehrere Formen zu haben ( autovs. decltype(auto)). Und so weiter.

Platzhalter funktionieren, weil sie die einfachste Lösung für das Problem darstellen und gleichzeitig alle Vorteile und die Allgemeingültigkeit der Verwendung eines tatsächlichen Typnamens beibehalten. Wenn Sie eine andere Alternative gefunden haben, die so universell funktioniert wie Platzhalter, ist es höchst unwahrscheinlich, dass sie so einfach wie Platzhalter ist.

Es sei denn, es wurden nur Platzhalter mit unterschiedlichen Schlüsselwörtern oder Symbolen geschrieben ...

Nicol Bolas
quelle
2
IMHO, dies ist die einzige Antwort, die einige wesentliche Gründe für die Wahl eines Platzhalters anspricht. Typabzug in generischem Lambda könnte ein weiteres Beispiel sein. Es ist schade, dass diese Antwort so wenige positive Stimmen bekommen hat, nur weil sie etwas spät veröffentlicht wurde ...
llllllllll
@liliscent: "Typabzug in generischem Lambda könnte ein weiteres Beispiel sein. " Ich habe das nicht erwähnt, weil es eine andere Semantik als autoDeklarationen / Rückgabewertabzug hat.
Nicol Bolas
@ Liliscent: In der Tat ist diese Antwort zu spät für die Partei. Aufgestockt. (Eine Verbesserung wäre jedoch die Erwähnung der C ++ - Idee, dass wenn etwas eine Erklärung sein könnte, es eine Erklärung ist .)
Bathseba
12

Kurz gesagt: autokönnte in einigen Fällen fallengelassen werden, aber das würde zu Inkonsistenzen führen.

Wie bereits erwähnt, lautet die Deklarationssyntax in C ++ zunächst einmal <type> <varname>. Explizite Deklarationen erfordern an ihrer Stelle einen Typ oder zumindest ein Deklarationsschlüsselwort. Wir könnten also var <varname>oder declare <varname>oder etwas verwenden, ist aber autoein langjähriges Schlüsselwort in C ++ und ein guter Kandidat für das Schlüsselwort für den automatischen Typabzug.

Ist es möglich, Variablen implizit durch Zuweisung zu deklarieren, ohne alles zu beschädigen?

Manchmal ja. Sie können keine Zuweisung außerhalb von Funktionen ausführen, daher können Sie dort die Zuweisungssyntax für Deklarationen verwenden. Ein solcher Ansatz würde jedoch zu Inkonsistenzen in der Sprache führen und möglicherweise zu menschlichen Fehlern führen.

a = 0; // Error. Could be parsed as auto declaration instead.
int main() {
  return 0;
}

Und wenn es um lokale Variablen geht, können explizite Deklarationen den Umfang einer Variablen steuern.

a = 1; // use a variable declared before or outside
auto b = 2; // declare a variable here

Wenn mehrdeutige Syntax zulässig wäre, könnte das Deklarieren globaler Variablen plötzlich lokale implizite Deklarationen in Zuweisungen konvertieren. Um diese Conversions zu finden, müsste alles überprüft werden . Und um Kollisionen zu vermeiden, benötigen Sie eindeutige Namen für alle Globals, wodurch die gesamte Idee des Scoping zerstört wird. Es ist also wirklich schlimm.

Andrew Svietlichnyy
quelle
11

autoist ein Schlüsselwort, das Sie an Stellen verwenden können, an denen Sie normalerweise einen Typ angeben müssen .

  int x = some_function();

Kann allgemeiner gestaltet werden, indem der intTyp automatisch abgeleitet wird:

  auto x = some_function();

Es ist also eine konservative Erweiterung der Sprache; es passt in die vorhandene Syntax. Ohne es x = some_function()wird eine Zuweisungserklärung, keine Erklärung mehr.

Rustyx
quelle
9

Die Syntax muss eindeutig und auch abwärtskompatibel sein.

Wenn auto gelöscht wird, kann nicht zwischen Anweisungen und Definitionen unterschieden werden.

auto n = 0; // fine
n=0; // statememt, n is undefined.
code707
quelle
3
Ein wichtiger Punkt ist, dass autoes sich bereits um ein Schlüsselwort handelte (jedoch mit veralteter Bedeutung), sodass der Code nicht beschädigt wurde, wenn er als Name verwendet wurde. Dies ist ein Grund, warum stattdessen kein besseres Keyword wie varoder letausgewählt wurde.
Frax
1
@Frax IMO autoist eigentlich ein ziemlich gutes Schlüsselwort dafür: Es drückt genau das aus, wofür es steht, nämlich es ersetzt einen Typnamen durch "den automatischen Typ". Bei einem Schlüsselwort wie varoder letsollten Sie folglich das Schlüsselwort auch dann benötigen , wenn der Typ explizit angegeben ist, dh var int n = 0oder so ähnlich var n:Int = 0. So wird es im Grunde in Rust gemacht.
links um
1
@leftaroundabout Obwohl dies autoim Kontext der vorhandenen Syntax definitiv hervorragend ist, würde ich sagen, dass so etwas wie var int x = 42die grundlegende Variablendefinition mit var x = 42und int x = 42als Kurzform sinnvoller wäre als die aktuelle Syntax, wenn sie aus historischem Inhalt betrachtet wird. Aber es ist meistens Geschmackssache. Aber Sie haben Recht, ich hätte "einen der Gründe" anstelle von "einen Grund" in meinen ursprünglichen Kommentar
schreiben sollen
@leftaroundabout: "auto ist eigentlich ein ziemlich gutes Schlüsselwort dafür: Es drückt genau aus, wofür es steht, nämlich es ersetzt einen Typnamen durch 'den automatischen Typ'" " Das ist nicht wahr. Es gibt keinen "automatischen Typ".
Leichtigkeitsrennen im Orbit
@LightnessRacesinOrbit In jedem Kontext, in dem Sie verwenden können auto, gibt es einen automatischen Typ (je nach Ausdruck einen anderen).
links um
3

Hinzu kommt eine zusätzliche Anmerkung zu einem alten Furz: Es sieht so aus, als ob Sie es als Vorteil ansehen, einfach eine neue Variable verwenden zu können, ohne sie in irgendeiner Weise zu deklarieren.

In Sprachen mit der Möglichkeit der impliziten Definition von Variablen kann dies ein großes Problem sein, insbesondere in größeren Systemen. Sie machen einen Tippfehler und debuggen stundenlang, nur um herauszufinden, dass Sie unbeabsichtigt eine Variable mit dem Wert Null (oder schlechter) eingeführt haben - bluevs bleu, labelvs lable... das Ergebnis ist, dass Sie keinem Code wirklich vertrauen können, ohne genau zu prüfen, ob er genau ist Variablennamen.

Nur mit autowird sowohl dem Compiler als auch dem Betreuer mitgeteilt, dass Sie beabsichtigen, eine neue Variable zu deklarieren.

Denken Sie darüber nach, um diese Art von Albträumen vermeiden zu können, wurde in FORTRAN die Aussage "implizit keine" eingeführt - und Sie sehen, dass sie heutzutage in allen seriösen FORTRAN-Programmen verwendet wird. Es nicht zu haben ist einfach ... beängstigend.

Bert Bril
quelle