¿Cómo funciona realmente el constructor const?

111

He notado que es posible crear un constructor constante en Dart. En la documentación, dice que la constpalabra se usa para denotar algo como una constante de tiempo de compilación.

Me preguntaba qué sucede cuando uso un constconstructor para crear un objeto. ¿Es esto como un objeto inmutable que siempre es el mismo y está disponible en tiempo de compilación? ¿Cómo funciona constrealmente el concepto de constructor? ¿En qué se diferencia un constructor constante de un constructor regular ?

Markovuksanovic
fuente

Respuestas:

78

El constructor Const crea una instancia "canonicalizada".

Es decir, todas las expresiones constantes comienzan canonicalizadas, y luego estos símbolos "canonicalizados" se utilizan para reconocer la equivalencia de estas constantes.

Canonicalización:

Un proceso para convertir datos que tienen más de una representación posible en una representación canónica "estándar". Esto se puede hacer para comparar diferentes representaciones para la equivalencia, contar el número de estructuras de datos distintas, mejorar la eficiencia de varios algoritmos eliminando cálculos repetidos, o hacer posible imponer un orden de clasificación significativo.


Esto significa que expresiones constantes como const Foo(1, 1) pueden representar cualquier forma utilizable que sea útil para la comparación en una máquina virtual.

La VM solo necesita tener en cuenta el tipo de valor y los argumentos en el orden en el que aparecen en esta expresión constante. Y, por supuesto, se reducen para optimizarlos.

Constantes con los mismos valores canonicalizados:

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

Constantes con diferentes valores canonicalizados (porque las firmas difieren):

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

Las constantes no se recrean cada vez. Se canonizan en el momento de la compilación y se almacenan en tablas de búsqueda especiales (donde se les aplica un hash por sus firmas canónicas) desde las que se reutilizan más tarde.

PD

El formulario #Foo#int#1#int#1utilizado en estos ejemplos solo se utiliza con fines de comparación y no es una forma real de canonicalización (representación) en Dart VM;

Pero la forma de canonicalización real debe ser la representación canónica "estándar".

mezoni
fuente
80

Creo que la respuesta de Lasse en el blog de Chris Storms es una gran explicación.

Constructores de Dart Constant

Espero que no les importe que copie el contenido.

Esta es una buena explicación de los campos finales, pero realmente no explica los constructores const. Nada en estos ejemplos usa realmente que los constructores sean constructores constantes. Cualquier clase puede tener campos finales, constructores constantes o no.

Un campo en Dart es realmente una ubicación de almacenamiento anónima combinada con un captador y configurador creado automáticamente que lee y actualiza el almacenamiento, y también se puede inicializar en una lista de inicializadores del constructor.

Un campo final es el mismo, solo que sin el setter, por lo que la única forma de establecer su valor es en la lista de inicializadores del constructor, y no hay forma de cambiar el valor después de eso - de ahí el "final".

El objetivo de los constructores const no es inicializar los campos finales, cualquier constructor generativo puede hacer eso. El punto es crear valores constantes en tiempo de compilación: objetos donde todos los valores de campo ya se conocen en el tiempo de compilación, sin ejecutar ninguna declaración.

Eso impone algunas restricciones a la clase y al constructor. Un constructor const no puede tener un cuerpo (¡no se ejecutan sentencias!) Y su clase no debe tener ningún campo no final (el valor que "conocemos" en el momento de la compilación no debe poder cambiar más adelante). La lista de inicializadores también debe inicializar campos a otras constantes en tiempo de compilación, por lo que los lados derechos están limitados a "expresiones constantes en tiempo de compilación" [1]. Y debe tener el prefijo "const"; de lo contrario, obtendrá un constructor normal que satisfaga esos requisitos. Eso está perfectamente bien, simplemente no es un constructor constante.

Para usar un constructor const para crear realmente un objeto constante en tiempo de compilación, luego reemplaza "nuevo" por "const" en una expresión "nueva". Aún puede usar "new" con un constructor const, y aún creará un objeto, pero será un nuevo objeto normal, no un valor constante en tiempo de compilación. Es decir: un constructor const también se puede usar como un constructor normal para crear objetos en tiempo de ejecución, así como para crear objetos constantes en tiempo de compilación en tiempo de compilación.

Entonces, como ejemplo:

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

Las constantes de tiempo de compilación se canonicalizan. Eso significa que no importa cuántas veces escriba "const Point (0,0)", solo crea un objeto. Eso puede ser útil, pero no tanto como parece, ya que puede crear una variable constante para mantener el valor y usar la variable en su lugar.

Entonces, ¿para qué sirven las constantes en tiempo de compilación?

  • Son útiles para enumeraciones.
  • Puede utilizar valores constantes en tiempo de compilación en casos de cambio.
  • Se utilizan como anotaciones.

Las constantes de tiempo de compilación solían ser más importantes antes de que Dart cambiara a la inicialización lenta de variables. Antes de eso, solo podía declarar una variable global inicializada como "var x = foo;" si "foo" fuera una constante en tiempo de compilación. Sin ese requisito, la mayoría de los programas se pueden escribir sin utilizar ningún objeto constante.

Entonces, breve resumen: los constructores Const son solo para crear valores constantes en tiempo de compilación.

/ L

[1] O realmente: "Expresiones constantes potencialmente en tiempo de compilación" porque también puede referirse a los parámetros del constructor. [2] Entonces, sí, una clase puede tener constructores const y no const al mismo tiempo.

Este tema también se discutió en https://github.com/dart-lang/sdk/issues/36079 con algunos comentarios interesantes.

Günter Zöchbauer
fuente
AFAIK const y final permiten generar JS más optimizados.
Günter Zöchbauer
2
También son útiles para valores predeterminados en firmas de métodos.
Florian Loitsch
1
¿Alguien puede explicarme cómo funciona esta línea? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas
¿Qué parte no está clara? No parece relacionado conconst
Günter Zöchbauer
3
constes una buena ganancia de rendimiento para los widgets de Flutter según medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "Use const para construir sus widgets Sin const, la reconstrucción selectiva del subárbol no ocurre. Flutter crea una nueva instancia de cada uno widget en el subárbol y llama a build () desperdiciando ciclos preciosos especialmente si sus métodos de construcción son pesados ​​".
David Chandler
8

Muy bien explicado en detalle, pero para los usuarios que realmente buscan el uso de un constructor const

Se usa para aumentar el rendimiento de Flutter, ya que ayuda a Flutter a reconstruir solo los widgets que deben actualizarse. Significa que mientras se usa setState () en StateFulWidgets, solo se reconstruirán aquellos componentes que no sean constructores constantes

Puede explicarse con un ejemplo->

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

Como en este ejemplo, solo se debe cambiar el título del texto, por lo que solo este widget debe reconstruirse, por lo que hacer que todos los demás widgets sean un constructor constante ayudará a que flutter haga lo mismo para aumentar el rendimiento.

B.shruti
fuente
0

Una demostración de ejemplo que la instancia constante realmente decide por campo final.
Y en este caso, no se puede predecir en tiempo de compilación.

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

Ahora dart lo comprobará.

Análisis de dardos:

[dardo] No se puede definir el constructor 'const' porque el campo 'j' se inicializa con un valor no constante

Error de tiempo de ejecución:

/main.dart ': error: línea 5 pos 17: la expresión no es una constante de tiempo de compilación válida final int j = new DateTime.now (). milisegundo;

Ticore shih
fuente