Como o construtor const realmente funciona?


111

Percebi que é possível criar um construtor const no Dart. Na documentação, diz que a constpalavra é usada para denotar algo uma constante de tempo de compilação.

Gostaria de saber o que acontece quando uso um constconstrutor para criar um objeto. É como um objeto imutável que é sempre o mesmo e disponível em tempo de compilação? Como o conceito de constconstrutor realmente funciona? Como um construtor const é diferente de um construtor regular ?

Respostas:


78

O construtor Const cria uma instância "canonizada".

Ou seja, todas as expressões constantes começam canonicalizadas e, posteriormente, esses símbolos "canonicalizados" são usados ​​para reconhecer a equivalência dessas constantes.

Canonização:

Um processo para converter dados que têm mais de uma representação possível em uma representação canônica "padrão". Isso pode ser feito para comparar diferentes representações de equivalência, para contar o número de estruturas de dados distintas, para melhorar a eficiência de vários algoritmos, eliminando cálculos repetidos, ou para tornar possível impor uma ordem de classificação significativa.


Isso significa que expressões const como const Foo(1, 1) podem representar qualquer forma utilizável que seja útil para comparação na máquina virtual.

A VM só precisa levar em consideração o tipo de valor e os argumentos na ordem em que ocorrem nesta expressão const. E, é claro, eles são reduzidos para otimização.

Constantes com os mesmos valores canônicos:

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

Constantes com diferentes valores canônicos (porque as assinaturas são diferentes):

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

As constantes não são recriadas todas as vezes. Eles são canônicos em tempo de compilação e armazenados em tabelas de pesquisa especiais (onde são hash por suas assinaturas canônicas) a partir das quais são reutilizados posteriormente.

PS

A forma #Foo#int#1#int#1 usado nesses exemplos é usado apenas para fins de comparação e não é uma forma real de canonização (representação) no Dart VM;

Mas a forma de canonização real deve ser a representação canônica "padrão".


80

Acho a resposta de Lasse no blog de Chris Storms uma ótima explicação.

Dart Constant Constructors

Espero que eles não se importem que eu copie o conteúdo.

Esta é uma boa explicação dos campos finais, mas não explica realmente os construtores const. Nada nesses exemplos realmente usa que os construtores sejam construtores const. Qualquer classe pode ter campos finais, construtores const ou não.

Um campo no Dart é realmente um local de armazenamento anônimo combinado com um getter e setter criado automaticamente que lê e atualiza o armazenamento, e também pode ser inicializado em uma lista de inicializadores de construtor.

Um campo final é o mesmo, apenas sem o setter, portanto, a única maneira de definir seu valor é na lista de inicializadores do construtor e não há como alterar o valor depois disso - daí o "final".

O objetivo dos construtores const não é inicializar os campos finais, qualquer construtor generativo pode fazer isso. O objetivo é criar valores constantes de tempo de compilação: Objetos onde todos os valores de campo já são conhecidos em tempo de compilação, sem executar nenhuma instrução.

Isso impõe algumas restrições à classe e ao construtor. Um construtor const não pode ter um corpo (nenhuma instrução executada!) E sua classe não deve ter nenhum campo não final (o valor que "sabemos" em tempo de compilação não deve ser capaz de mudar posteriormente). A lista de inicializadores também deve inicializar campos para outras constantes de tempo de compilação, portanto, os lados direitos são limitados a "expressões constantes de tempo de compilação" [1]. E deve ser prefixado com "const" - caso contrário, você obtém apenas um construtor normal que satisfaz esses requisitos. Isso é perfeitamente normal, mas não é um construtor const.

Para usar um construtor const para realmente criar um objeto constante de tempo de compilação, você substitui "novo" por "const" em uma "nova" expressão. Você ainda pode usar "new" com um construtor const, e ele ainda criará um objeto, mas será apenas um novo objeto normal, não um valor constante de tempo de compilação. Isto é: Um construtor const também pode ser usado como um construtor normal para criar objetos em tempo de execução, bem como criar objetos constantes de tempo de compilação em tempo de compilação.

Então, como exemplo:

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

As constantes de tempo de compilação são canônicas. Isso significa que não importa quantas vezes você escreva "const Point (0,0)", você cria apenas um objeto. Isso pode ser útil - mas não tanto quanto parece, já que você pode simplesmente fazer uma variável const para manter o valor e usar a variável em seu lugar.

Então, para que servem as constantes de tempo de compilação?

  • Eles são úteis para enums.
  • Você pode usar valores constantes de tempo de compilação em casos de switch.
  • Eles são usados ​​como anotações.

As constantes de tempo de compilação costumavam ser mais importantes antes de o Dart mudar para as variáveis ​​de inicialização lenta. Antes disso, você só podia declarar uma variável global inicializada como "var x = foo;" se "foo" for uma constante de tempo de compilação. Sem esse requisito, a maioria dos programas pode ser escrita sem usar nenhum objeto const

Portanto, um breve resumo: construtores Const são apenas para criar valores constantes de tempo de compilação.

/EU

[1] Ou realmente: "Potencialmente expressões constantes de tempo de compilação" porque também pode se referir aos parâmetros do construtor. [2] Então, sim, uma classe pode ter construtores const e não const ao mesmo tempo.

Este tópico também foi discutido em https://github.com/dart-lang/sdk/issues/36079 com alguns comentários interessantes.


Const e final AFAIK permitem gerar JS mais otimizado.
Günter Zöchbauer

2
Eles também são úteis para valores padrão em assinaturas de método.
Florian Loitsch

1
Alguém pode me explicar como funciona essa linha? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas

Que parte não está clara? Não parece relacionado aconst
Günter Zöchbauer

3
consté uma boa vitória de desempenho para widgets Flutter de acordo com medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "Use const para construir seus widgets Sem const, a reconstrução seletiva da subárvore não acontece. Flutter cria uma nova instância de cada widget na subárvore e chama build () desperdiçando ciclos preciosos, especialmente se seus métodos de construção forem pesados. "
David Chandler

8

Muito bem explicado em detalhes, mas para os usuários que estão realmente procurando o uso de um construtor const

Ele é usado para aumentar o desempenho do Flutter, pois ajuda o Flutter a reconstruir apenas widgets que devem ser atualizados.

Pode ser explicado com um exemplo->

    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 neste exemplo, apenas o título do texto deve ser alterado, portanto, apenas este widget deve ser reconstruído, portanto, fazer todos os outros widgets como construtor const ajudará a fazer o mesmo para aumentar o desempenho.


0

Um exemplo de demonstração em que a instância const realmente decide pelo campo final.
E, neste caso, não pode ser previsto em tempo de compilação.

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

Agora o dardo vai verificar.

Análise de dardo:

[dardo] Não é possível definir o construtor 'const' porque o campo 'j' foi inicializado com um valor não constante

Erro de tempo de execução:

/main.dart ': erro: linha 5 pos 17: expressão não é uma constante de tempo de compilação válida final int j = new DateTime.now (). milissegundo;

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.