Wie funktioniert der const-Konstruktor tatsächlich?


111

Ich habe festgestellt, dass es möglich ist, einen Const-Konstruktor in Dart zu erstellen. In der Dokumentation steht dasconst Wort verwendet wird, um eine Kompilierungszeitkonstante zu bezeichnen.

Ich habe mich gefragt, was passiert, wenn ich constein Objekt mit einem Konstruktor erstelle. Ist dies wie ein unveränderliches Objekt, das immer gleich ist und zur Kompilierungszeit verfügbar ist? Wie funktioniert das Konzept des constKonstruktors tatsächlich? Wie unterscheidet sich ein const- Konstruktor von einem regulären Konstruktor?

Antworten:


78

Der Const-Konstruktor erstellt eine "kanonisierte" Instanz.

Das heißt, alle konstanten Ausdrücke beginnen kanonisiert, und später werden diese "kanonisierten" Symbole verwendet, um die Äquivalenz dieser Konstanten zu erkennen.

Kanonisierung:

Ein Prozess zum Konvertieren von Daten mit mehr als einer möglichen Darstellung in eine "standardmäßige" kanonische Darstellung. Dies kann erfolgen, um verschiedene Darstellungen auf Äquivalenz zu vergleichen, die Anzahl unterschiedlicher Datenstrukturen zu zählen, die Effizienz verschiedener Algorithmen durch Eliminieren wiederholter Berechnungen zu verbessern oder um eine aussagekräftige Sortierreihenfolge festzulegen.


Dies bedeutet, dass const Ausdrücke wie const Foo(1, 1) jede verwendbare Form darstellen können, die für den Vergleich in virtuellen Maschinen nützlich ist.

Die VM muss nur den Wertetyp und die Argumente in der Reihenfolge berücksichtigen, in der sie in diesem const-Ausdruck vorkommen. Und natürlich werden sie zur Optimierung reduziert.

Konstanten mit denselben kanonisierten Werten:

var foo1 = const Foo(1, 1); // #Foo#int#1#int#1
var foo2 = const Foo(1, 1); // #Foo#int#1#int#1

Konstanten mit unterschiedlichen kanonisierten Werten (weil sich die Signaturen unterscheiden):

var foo3 = const Foo(1, 2); // $Foo$int$1$int$2
var foo4 = const Foo(1, 3); // $Foo$int$1$int$3

var baz1 = const Baz(const Foo(1, 1), "hello"); // $Baz$Foo$int$1$int$1$String$hello
var baz2 = const Baz(const Foo(1, 1), "hello"); // $Baz$Foo$int$1$int$1$String$hello

Konstanten werden nicht jedes Mal neu erstellt. Sie werden zur Kompilierungszeit kanonisiert und in speziellen Nachschlagetabellen gespeichert (wo sie durch ihre kanonischen Signaturen gehasht werden), aus denen sie später wiederverwendet werden.

PS

Die #Foo#int#1#int#1in diesen Beispielen verwendete Form wird nur zu Vergleichszwecken verwendet und ist keine echte Form der Kanonisierung (Darstellung) in Dart VM.

Die eigentliche Kanonisierungsform muss jedoch eine "standardmäßige" kanonische Darstellung sein.


80

Ich finde Masses Antwort auf Chris Storms Blog eine großartige Erklärung.

Dart Constant Konstruktoren

Ich hoffe, es macht ihnen nichts aus, dass ich den Inhalt kopiere.

Dies ist eine gute Erklärung für die letzten Felder, aber es erklärt nicht wirklich const-Konstruktoren. Nichts in diesen Beispielen verwendet tatsächlich, dass die Konstruktoren const-Konstruktoren sind. Jede Klasse kann endgültige Felder, const-Konstruktoren oder nicht haben.

Ein Feld in Dart ist in Wirklichkeit ein anonymer Speicherort in Kombination mit einem automatisch erstellten Getter und Setter, der den Speicher liest und aktualisiert. Er kann auch in der Initialisierungsliste eines Konstruktors initialisiert werden.

Ein letztes Feld ist dasselbe, nur ohne den Setter. Die einzige Möglichkeit, seinen Wert festzulegen, besteht in der Konstruktorinitialisiererliste, und es gibt keine Möglichkeit, den Wert danach zu ändern - daher das "endgültige".

Der Sinn von const-Konstruktoren besteht nicht darin, endgültige Felder zu initialisieren. Jeder generative Konstruktor kann dies tun. Es geht darum, Konstantenwerte zur Kompilierungszeit zu erstellen: Objekte, bei denen alle Feldwerte bereits zur Kompilierungszeit bekannt sind, ohne dass Anweisungen ausgeführt werden.

Das schränkt die Klasse und den Konstruktor ein. Ein const-Konstruktor kann keinen Body haben (keine Anweisungen ausgeführt!) Und seine Klasse darf keine nicht endgültigen Felder haben (der Wert, den wir zur Kompilierungszeit "kennen", darf sich später nicht ändern können). Die Initialisierungsliste darf auch Felder nur mit anderen Konstanten zur Kompilierungszeit initialisieren, sodass die rechten Seiten auf "Konstantenausdrücke zur Kompilierungszeit" beschränkt sind [1]. Und es muss "const" vorangestellt werden - andernfalls erhalten Sie nur einen normalen Konstruktor, der diese Anforderungen erfüllt. Das ist vollkommen in Ordnung, es ist einfach kein const-Konstruktor.

Um einen const-Konstruktor zu verwenden, um tatsächlich ein Konstantenobjekt zur Kompilierungszeit zu erstellen, ersetzen Sie "new" in einem "new" -Ausdruck durch "const". Sie können "new" weiterhin mit einem const-Konstruktor verwenden, und es wird weiterhin ein Objekt erstellt, es handelt sich jedoch nur um ein normales neues Objekt und nicht um einen konstanten Wert zur Kompilierungszeit. Das heißt: Ein const-Konstruktor kann auch als normaler Konstruktor verwendet werden, um Objekte zur Laufzeit sowie konstante Objekte zur Kompilierungszeit zur Kompilierungszeit zu erstellen.

Also als Beispiel:

class Point { 
  static final Point ORIGIN = const Point(0, 0); 
  final int x; 
  final int y; 
  const Point(this.x, this.y);
  Point.clone(Point other): x = other.x, y = other.y; //[2] 
}

main() { 
  // Assign compile-time constant to p0. 
  Point p0 = Point.ORIGIN; 
  // Create new point using const constructor. 
  Point p1 = new Point(0, 0); 
  // Create new point using non-const constructor.
  Point p2 = new Point.clone(p0); 
  // Assign (the same) compile-time constant to p3. 
  Point p3 = const Point(0, 0); 
  print(identical(p0, p1)); // false 
  print(identical(p0, p2)); // false 
  print(identical(p0, p3)); // true! 
}

Konstanten zur Kompilierungszeit werden kanonisiert. Das heißt, egal wie oft Sie "const Point (0,0)" schreiben, Sie erstellen nur ein Objekt. Das mag nützlich sein - aber nicht so viel, wie es scheint, da Sie einfach eine const-Variable erstellen können, die den Wert enthält, und stattdessen die Variable verwenden.

Wofür sind Konstanten zur Kompilierungszeit überhaupt gut?

  • Sie sind nützlich für Aufzählungen.
  • In Switch-Fällen können Sie Konstantenwerte zur Kompilierungszeit verwenden.
  • Sie werden als Anmerkungen verwendet.

Konstanten zur Kompilierungszeit waren früher wichtiger, bevor Dart auf träge Initialisierung von Variablen umstellte. Vorher konnten Sie nur eine initialisierte globale Variable wie "var x = foo;" deklarieren. wenn "foo" eine Kompilierungszeitkonstante war. Ohne diese Anforderung können die meisten Programme ohne Verwendung von const-Objekten geschrieben werden

Kurze Zusammenfassung: Const-Konstruktoren dienen nur zum Erstellen von Konstantenwerten zur Kompilierungszeit.

/ L.

[1] Oder wirklich: "Konstante Ausdrücke zur Kompilierungszeit", da sie sich möglicherweise auch auf die Konstruktorparameter beziehen. [2] Ja, eine Klasse kann gleichzeitig sowohl const- als auch non-const-Konstruktoren haben.

Dieses Thema wurde auch in https://github.com/dart-lang/sdk/issues/36079 mit einigen interessanten Kommentaren behandelt.


AFAIK const und final ermöglichen die Generierung optimierterer JS.
Günter Zöchbauer

2
Sie sind auch nützlich für Standardwerte in Methodensignaturen.
Florian Loitsch

1
Kann mir jemand erklären, wie diese Linie funktioniert? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas

Welcher Teil ist unklar? Es sieht nicht verwandt aus mitconst
Günter Zöchbauer

3
constist ein guter Leistungsgewinn für Flutter-Widgets gemäß medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "Verwenden Sie const, um Ihre Widgets zu erstellen. Ohne const findet keine selektive Neuerstellung des Unterbaums statt. Flutter erstellt jeweils eine neue Instanz Widget im Unterbaum und Aufrufe von build (), die wertvolle Zyklen verschwenden, insbesondere wenn Ihre Erstellungsmethoden schwer sind. "
David Chandler

8

Sehr gut im Detail erklärt, aber für die Benutzer, die tatsächlich nach der Verwendung eines const-Konstruktors suchen

Es wird verwendet, um die Flutter-Leistung zu erhöhen, da es Flutter hilft, nur Widgets neu zu erstellen, die aktualisiert werden sollen. Bei Verwendung von setState () in StateFulWidgets werden nur die Komponenten neu erstellt, die kein Const-Konstruktor sind

Kann mit Beispiel-> erklärt werden

    class _MyWidgetState extends State<MyWidget> {

  String title = "Title";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: <Widget>[
          const Text("Text 1"),
          const Padding(
            padding: const EdgeInsets.all(8.0),
            child: const Text("Another Text widget"),
          ),
          const Text("Text 3"),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          setState(() => title = 'New Title');
        },
      ),
    );
  }
}

Wie in diesem Beispiel sollte nur der Texttitel geändert werden, sodass nur dieses Widget neu erstellt werden sollte. Wenn Sie also alle anderen Widgets als const-Konstruktor festlegen, können Sie das Gleiche tun, um die Leistung zu steigern.


0

Eine Beispieldemo, die diese const-Instanz wirklich nach dem letzten Feld entscheidet.
In diesem Fall kann dies in der Kompilierungszeit nicht vorhergesagt werden.

import 'dart:async';

class Foo {
  final int i;
  final int j = new DateTime.now().millisecond;
  const Foo(i) : this.i = i ~/ 10;

  toString() => "Foo($i, $j)";
}



void main() {
  var f2 = const Foo(2);
  var f3 = const Foo(3);

  print("f2 == f3 : ${f2 == f3}"); // true
  print("f2 : $f2"); // f2 : Foo(0, 598)
  print("f3 : $f3"); // f3 : Foo(0, 598)

  new Future.value().then((_) {
    var f2i = const Foo(2);
    print("f2 == f2i : ${f2 == f2i}"); // false
    print("f2i : $f2i"); // f2i : Foo(0, 608)
  });
}

Jetzt wird Dart es überprüfen.

Dart-Analyse:

[dart] Der Konstruktor 'const' kann nicht definiert werden, da das Feld 'j' mit einem nicht konstanten Wert initialisiert wird

Laufzeit Fehler:

/main.dart ': Fehler: Zeile 5 Pos. 17: Ausdruck ist keine gültige Konstante zur Kompilierungszeit final int j = new DateTime.now (). Millisekunde;

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.