Come funziona effettivamente il costruttore const?


112

Ho notato che è possibile creare un costruttore const in Dart. Nella documentazione, dice che la constparola è usata per denotare qualcosa una costante del tempo di compilazione.

Mi chiedevo cosa succede quando utilizzo un constcostruttore per creare un oggetto. È come un oggetto immutabile che è sempre lo stesso e disponibile in fase di compilazione? Come funziona consteffettivamente il concetto di costruttore? In che modo un costruttore const è diverso da un normale costruttore?

Risposte:


78

Il costruttore Const crea un'istanza "canonizzata".

Cioè, tutte le espressioni costanti iniziano canonicalizzate e successivamente questi simboli "canonizzati" vengono usati per riconoscere l'equivalenza di queste costanti.

canonicalization:

Un processo per convertire i dati che hanno più di una possibile rappresentazione in una rappresentazione canonica "standard". Questo può essere fatto per confrontare diverse rappresentazioni per l'equivalenza, per contare il numero di strutture di dati distinte, per migliorare l'efficienza di vari algoritmi eliminando calcoli ripetuti o per rendere possibile imporre un ordinamento significativo.


Ciò significa che espressioni const come const Foo(1, 1)possono rappresentare qualsiasi forma utilizzabile utile per il confronto nella macchina virtuale.

La VM deve solo prendere in considerazione il tipo di valore e gli argomenti nell'ordine in cui si trovano in questa espressione const. E, naturalmente, sono ridotti per l'ottimizzazione.

Costanti con gli stessi valori canonizzati:

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

Costanti con valori canonizzati diversi (perché le firme differiscono):

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

Le costanti non vengono ricreate ogni volta. Sono canonizzati in fase di compilazione e archiviati in tabelle di ricerca speciali (dove vengono sottoposti a hashing dalle loro firme canoniche) da cui vengono successivamente riutilizzati.

PS

Il modulo #Foo#int#1#int#1utilizzato in questi esempi viene utilizzato solo a scopo di confronto e non è una vera forma di canonicalizzazione (rappresentazione) in Dart VM;

Ma la vera forma di canonizzazione deve essere la rappresentazione canonica "standard".


81

Trovo che la risposta di Lasse sul blog di Chris Storms sia un'ottima spiegazione.

Dart Constant Constructors

Spero che a loro non dispiaccia che copio il contenuto.

Questa è una bella spiegazione dei campi finali, ma in realtà non spiega i costruttori const. Niente in questi esempi utilizza effettivamente il fatto che i costruttori siano costruttori const. Ogni classe può avere campi finali, costruttori const o meno.

Un campo in Dart è in realtà una posizione di archiviazione anonima combinata con un getter e un setter creati automaticamente che legge e aggiorna l'archiviazione e può anche essere inizializzato nell'elenco di inizializzatori di un costruttore.

Un campo finale è lo stesso, solo senza il setter, quindi l'unico modo per impostare il suo valore è nell'elenco degli inizializzatori del costruttore, e non c'è modo di cambiare il valore dopo di che - da qui il "finale".

Lo scopo dei costruttori const non è inizializzare i campi finali, qualsiasi costruttore generativo può farlo. Il punto è creare valori costanti in fase di compilazione: oggetti in cui tutti i valori dei campi sono noti già in fase di compilazione, senza eseguire alcuna istruzione.

Ciò pone alcune limitazioni alla classe e al costruttore. Un costruttore const non può avere un corpo (nessuna istruzione eseguita!) E la sua classe non deve avere campi non finali (il valore che "conosciamo" in fase di compilazione non deve essere in grado di cambiare in seguito). L'elenco degli inizializzatori deve anche inizializzare solo i campi su altre costanti in fase di compilazione, quindi i lati di destra sono limitati a "espressioni costanti in fase di compilazione" [1]. E deve essere preceduto da "const", altrimenti si ottiene solo un normale costruttore che soddisfa tali requisiti. Va benissimo, semplicemente non è un costruttore const.

Per utilizzare un costruttore const per creare effettivamente un oggetto costante in fase di compilazione, sostituire "new" con "const" in una "nuova" espressione. Puoi ancora usare "new" con un costruttore const, e creerà ancora un oggetto, ma sarà solo un normale nuovo oggetto, non un valore costante del tempo di compilazione. Ovvero: un costruttore const può essere utilizzato anche come un normale costruttore per creare oggetti in fase di esecuzione, oltre a creare oggetti costanti in fase di compilazione in fase di compilazione.

Quindi, ad esempio:

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! 
}

Le costanti in fase di compilazione sono canonizzate. Ciò significa che non importa quante volte scrivi "const Point (0,0)", crei solo un oggetto. Potrebbe essere utile, ma non quanto sembrerebbe, poiché puoi semplicemente creare una variabile const per contenere il valore e utilizzare invece la variabile.

Allora, a cosa servono comunque le costanti in fase di compilazione?

  • Sono utili per le enumerazioni.
  • È possibile utilizzare i valori delle costanti del tempo di compilazione nei casi di switch.
  • Sono usati come annotazioni.

Le costanti in fase di compilazione erano più importanti prima che Dart passasse all'inizializzazione pigra delle variabili. Prima di allora, potevi solo dichiarare una variabile globale inizializzata come "var x = foo;" se "foo" fosse una costante del tempo di compilazione. Senza questo requisito, la maggior parte dei programmi può essere scritta senza utilizzare alcun oggetto const

Quindi, breve sommario: i costruttori Const servono solo per creare valori di costanti in fase di compilazione.

/ L

[1] O in realtà: "Espressioni di costanti del tempo di compilazione potenzialmente" perché può anche fare riferimento ai parametri del costruttore. [2] Quindi sì, una classe può avere contemporaneamente costruttori const e non const.

Questo argomento è stato discusso anche in https://github.com/dart-lang/sdk/issues/36079 con alcuni commenti interessanti.


AFAIK const e final consentono di generare JS più ottimizzati.
Günter Zöchbauer

2
Sono anche utili per i valori predefiniti nelle firme dei metodi.
Florian Loitsch

1
Qualcuno può spiegarmi che come funziona questa linea? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas

Quale parte non è chiara? Non sembra correlato aconst
Günter Zöchbauer

3
constè una bella vittoria in termini di prestazioni per i widget Flutter secondo medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "Usa const per costruire i tuoi widget Senza const, la ricostruzione selettiva del sottoalbero non avviene. Flutter crea una nuova istanza di ciascuno widget nel sottoalbero e chiama build () sprecando cicli preziosi soprattutto se i tuoi metodi di compilazione sono pesanti. "
David Chandler

8

Molto ben spiegato in dettaglio ma per gli utenti che stanno effettivamente cercando l'utilizzo di un costruttore const

Viene utilizzato per aumentare le prestazioni di Flutter poiché aiuta Flutter a ricostruire solo i widget che dovrebbero essere aggiornati Significa che durante l'utilizzo di setState () in StateFulWidgets, verranno ricostruiti solo i componenti che non sono un costruttore const

Può essere spiegato con un esempio->

    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');
        },
      ),
    );
  }
}

Come in questo esempio, solo il titolo del testo dovrebbe essere modificato, quindi solo questo widget dovrebbe essere ricostruito, quindi rendere tutti gli altri widget come costruttore const aiuterà Flutter a fare lo stesso per aumentare le prestazioni.


0

Una demo di esempio in cui l'istanza const decide davvero in base al campo finale.
E in questo caso, non può essere previsto in fase di compilazione.

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)
  });
}

Ora Dart lo controllerà.

Analisi delle freccette:

[dart] Impossibile definire il costruttore "const" perché il campo "j" è inizializzato con un valore non costante

Errore di runtime:

/main.dart ': errore: riga 5 pos 17: l'espressione non è una costante di compilazione valida final int j = new DateTime.now (). millisecond;

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.