Comment fonctionne réellement le constructeur const?

112

J'ai remarqué qu'il est possible de créer un constructeur const dans Dart. Dans la documentation, il est dit que ce constmot est utilisé pour désigner quelque chose comme une constante de temps de compilation.

Je me demandais ce qui se passe lorsque j'utilise un constconstructeur pour créer un objet. Est-ce comme un objet immuable qui est toujours le même et disponible au moment de la compilation? Comment fonctionne constréellement le concept de constructeur? En quoi un constructeur const est-il différent d'un constructeur régulier ?

markovuksanovic
la source

Réponses:

79

Le constructeur Const crée une instance "canonisée".

C'est-à-dire que toutes les expressions constantes commencent par canonisation, et plus tard ces symboles «canonisés» sont utilisés pour reconnaître l'équivalence de ces constantes.

Canonisation:

Un processus pour convertir des données qui ont plus d'une représentation possible en une représentation canonique «standard». Cela peut être fait pour comparer différentes représentations d'équivalence, pour compter le nombre de structures de données distinctes, pour améliorer l'efficacité de divers algorithmes en éliminant les calculs répétés, ou pour permettre d'imposer un ordre de tri significatif.


Cela signifie que les expressions const comme const Foo(1, 1)peuvent représenter n'importe quelle forme utilisable qui est utile pour la comparaison dans une machine virtuelle.

La machine virtuelle doit uniquement prendre en compte le type de valeur et les arguments dans l'ordre dans lequel ils apparaissent dans cette expression const. Et, bien sûr, ils sont réduits pour l'optimisation.

Constantes avec les mêmes valeurs canoniques:

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

Constantes avec des valeurs canoniques différentes (car les signatures diffèrent):

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

Les constantes ne sont pas recréées à chaque fois. Ils sont canonisés au moment de la compilation et stockés dans des tables de recherche spéciales (où ils sont hachés par leurs signatures canoniques) à partir desquels ils sont ensuite réutilisés.

PS

La forme #Foo#int#1#int#1utilisée dans ces exemples n'est utilisée qu'à des fins de comparaison et ce n'est pas une vraie forme de canonisation (représentation) dans Dart VM;

Mais la vraie forme de canonisation doit être une représentation canonique «standard».

mezoni
la source
81

Je trouve la réponse de Lasse sur le blog de Chris Storms une excellente explication.

Constructeurs de constante Dart

J'espère que cela ne les dérange pas que je copie le contenu.

C'est une belle explication des champs finaux, mais cela n'explique pas vraiment les constructeurs const. Rien dans ces exemples n'utilise réellement que les constructeurs sont des constructeurs const. Toute classe peut avoir des champs finaux, constructeurs const ou non.

Un champ dans Dart est en réalité un emplacement de stockage anonyme combiné avec un getter et un setter créés automatiquement qui lit et met à jour le stockage, et il peut également être initialisé dans la liste d'initialisation d'un constructeur.

Un champ final est le même, juste sans le setter, donc la seule façon de définir sa valeur est dans la liste d'initialisation du constructeur, et il n'y a aucun moyen de changer la valeur après cela - d'où le "final".

Le but des constructeurs const n'est pas d'initialiser les champs finaux, n'importe quel constructeur génératif peut le faire. Le but est de créer des valeurs constantes au moment de la compilation: des objets où toutes les valeurs de champ sont déjà connues au moment de la compilation, sans exécuter aucune instruction.

Cela met quelques restrictions sur la classe et le constructeur. Un constructeur const ne peut pas avoir de corps (aucune instruction exécutée!) Et sa classe ne doit pas avoir de champs non finaux (la valeur que nous "connaissons" au moment de la compilation ne doit pas pouvoir changer plus tard). La liste d'initialisation doit également initialiser les champs uniquement avec d'autres constantes au moment de la compilation, de sorte que les côtés droits sont limités aux "expressions de constante au moment de la compilation" [1]. Et il doit être précédé de "const" - sinon, vous obtenez juste un constructeur normal qui satisfait à ces exigences. C'est parfaitement bien, ce n'est tout simplement pas un constructeur const.

Afin d'utiliser un constructeur const pour créer réellement un objet constant au moment de la compilation, vous remplacez alors "new" par "const" dans une "new" -expression. Vous pouvez toujours utiliser "new" avec un constructeur const, et il créera toujours un objet, mais ce sera juste un nouvel objet normal, pas une valeur constante à la compilation. Autrement dit: un constructeur const peut également être utilisé comme constructeur normal pour créer des objets au moment de l'exécution, ainsi que pour créer des objets constants au moment de la compilation au moment de la compilation.

Donc, à titre d'exemple:

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

Les constantes au moment de la compilation sont canonisées. Cela signifie que peu importe le nombre de fois que vous écrivez "const Point (0,0)", vous ne créez qu'un seul objet. Cela peut être utile - mais pas autant qu'il n'y paraît, car vous pouvez simplement créer une variable const pour contenir la valeur et utiliser la variable à la place.

Alors, à quoi servent les constantes de compilation?

  • Ils sont utiles pour les énumérations.
  • Vous pouvez utiliser des valeurs constantes au moment de la compilation dans les cas de commutation.
  • Ils sont utilisés comme annotations.

Les constantes au moment de la compilation étaient plus importantes avant que Dart ne passe à l'initialisation paresseuse des variables. Avant cela, vous ne pouviez déclarer qu'une variable globale initialisée comme "var x = foo;" si "foo" était une constante de compilation. Sans cette exigence, la plupart des programmes peuvent être écrits sans utiliser d'objets const

Donc, bref résumé: les constructeurs Const sont juste pour créer des valeurs constantes au moment de la compilation.

/ L

[1] Ou vraiment: "Potentiellement des expressions constantes au moment de la compilation" car cela peut aussi faire référence aux paramètres du constructeur. [2] Donc oui, une classe peut avoir à la fois des constructeurs const et non const.

Ce sujet a également été abordé dans https://github.com/dart-lang/sdk/issues/36079 avec quelques commentaires intéressants.

Günter Zöchbauer
la source
AFAIK const et final permettent de générer des JS plus optimisés.
Günter Zöchbauer
2
Ils sont également utiles pour les valeurs par défaut dans les signatures de méthode.
Florian Loitsch
1
Quelqu'un peut-il m'expliquer comment fonctionne cette ligne? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas
Quelle partie n'est pas claire? Il ne semble pas lié àconst
Günter Zöchbauer
3
constest une belle performance pour les widgets Flutter selon medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "Utilisez const pour construire vos widgets Sans const, la reconstruction sélective du sous-arbre ne se produit pas. Flutter crée une nouvelle instance de chaque widget dans le sous-arbre et appelle build () gaspillant de précieux cycles, surtout si vos méthodes de construction sont lourdes. "
David Chandler
8

Très bien expliqué en détail mais pour les utilisateurs qui recherchent réellement l'utilisation d'un constructeur const

Il est utilisé pour augmenter les performances de Flutter car il aide Flutter à reconstruire uniquement les widgets qui doivent être mis à jour.En utilisant setState () dans StateFulWidgets, seuls les composants qui ne sont pas des constructeurs const seront reconstruits

Peut être expliqué avec un exemple->

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

Comme dans cet exemple, seul le titre du texte doit être changé, donc seul ce widget doit être reconstruit, donc faire de tous les autres widgets en tant que constructeur const aidera à faire de même pour augmenter les performances.

B.shruti
la source
0

Un exemple de démonstration que l'instance const décide vraiment par champ final.
Et dans ce cas, il ne peut pas être prédit au moment de la compilation.

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

Maintenant, Dart va le vérifier.

Analyse de fléchettes:

[dart] Impossible de définir le constructeur 'const' car le champ 'j' est initialisé avec une valeur non constante

Erreur d'exécution:

/main.dart ': erreur: ligne 5 pos 17: l'expression n'est pas une constante de compilation valide final int j = new DateTime.now (). millisecond;

Ticore Shih
la source