Как на самом деле работает конструктор const?

113

Я заметил, что в Dart можно создать конструктор const. В документации сказано, чтоconst слово используется для обозначения чего-либо, постоянной времени компиляции.

Мне было интересно, что происходит, когда я использую constконструктор для создания объекта. Это как неизменный объект, который всегда один и тот же и доступен во время компиляции? Как на constсамом деле работает концепция конструктора? Чем константный конструктор отличается от обычного конструктора?

Марковуксанович
источник

Ответы:

81

Конструктор Const создает "канонизированный" экземпляр.

То есть все константные выражения начинаются с канонизации, а затем эти «канонические» символы используются для распознавания эквивалентности этих констант.

Канонизация:

Процесс преобразования данных, которые имеют более одного возможного представления, в «стандартное» каноническое представление. Это может быть сделано для сравнения различных представлений на эквивалентность, для подсчета количества различных структур данных, для повышения эффективности различных алгоритмов за счет исключения повторяющихся вычислений или для обеспечения возможности наложения значимого порядка сортировки.


Это означает, что такие константные выражения const Foo(1, 1)могут представлять любую пригодную для использования форму, которая полезна для сравнения на виртуальной машине.

Виртуальной машине необходимо учитывать только тип значения и аргументы в том порядке, в котором они встречаются в этом константном выражении. И, конечно же, для оптимизации они уменьшены.

Константы с одинаковыми канонизированными значениями:

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

Константы с разными канонизированными значениями (поскольку подписи различаются):

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

Константы не воссоздаются каждый раз. Они канонизируются во время компиляции и хранятся в специальных таблицах поиска (где они хешируются по их каноническим подписям), из которых они позже используются повторно.

PS

Форма, #Foo#int#1#int#1используемая в этих примерах, используется только в целях сравнения и не является реальной формой канонизации (представления) в Dart VM;

Но настоящей формой канонизации должно быть «стандартное» каноническое представление.

мезони
источник
82

Я нахожу ответ Лассе в блоге Криса Стормса прекрасным объяснением.

Конструкторы констант Dart

Надеюсь, они не возражают, что я скопирую контент.

Это прекрасное объяснение полей final, но на самом деле оно не объясняет конструкторы const. Фактически ничто в этих примерах не использует то, что конструкторы являются конструкторами const. Любой класс может иметь поля final, конструкторы const или нет.

Поле в Dart на самом деле является анонимным хранилищем в сочетании с автоматически созданным геттером и сеттером, который считывает и обновляет хранилище, а также его можно инициализировать в списке инициализаторов конструктора.

Последнее поле такое же, только без установщика, поэтому единственный способ установить его значение - в списке инициализаторов конструктора, и нет возможности изменить значение после этого - отсюда и «final».

Суть конструкторов const не в том, чтобы инициализировать поля final, это может сделать любой генеративный конструктор. Дело в том, чтобы создать значения констант времени компиляции: объекты, в которых все значения полей известны уже во время компиляции, без выполнения каких-либо операторов.

Это накладывает некоторые ограничения на класс и конструктор. Конструктор const не может иметь тела (никаких выполняемых операторов!), А его класс не должен иметь каких-либо незавершенных полей (значение, которое мы «знаем» во время компиляции, не должно измениться позже). Список инициализаторов должен также инициализировать поля только другими константами времени компиляции, поэтому правая часть ограничена «выражениями констант времени компиляции» [1]. И он должен иметь префикс «const» - иначе вы просто получите нормальный конструктор, который удовлетворяет этим требованиям. Это прекрасно, просто это не константный конструктор.

Чтобы использовать конструктор const для фактического создания объекта-константы времени компиляции, вы затем заменяете «new» на «const» в выражении «new». Вы по-прежнему можете использовать "new" с константным конструктором, и он по-прежнему будет создавать объект, но это будет просто обычный новый объект, а не постоянное значение времени компиляции. То есть: конструктор const также может использоваться как обычный конструктор для создания объектов во время выполнения, а также для создания объектов-констант времени компиляции во время компиляции.

Итак, в качестве примера:

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

Константы времени компиляции канонизированы. Это означает, что сколько бы раз вы ни писали «const Point (0,0)», вы создаете только один объект. Это может быть полезно, но не так сильно, как могло бы показаться, поскольку вы можете просто создать константную переменную для хранения значения и вместо нее использовать переменную.

Итак, для чего вообще нужны константы времени компиляции?

  • Они полезны для перечислений.
  • Вы можете использовать значения констант времени компиляции в случаях переключения.
  • Они используются как аннотации.

Константы времени компиляции были более важными до того, как Дарт перешел на ленивую инициализацию переменных. До этого вы могли объявлять только инициализированную глобальную переменную, например «var x = foo;» если "foo" было константой времени компиляции. Без этого требования большинство программ можно писать без использования каких-либо константных объектов.

Итак, краткое резюме: конструкторы Const предназначены только для создания значений констант времени компиляции.

/ Л

[1] Или на самом деле: «Выражения с потенциальной константой времени компиляции», потому что это также может относиться к параметрам конструктора. [2] Итак, да, класс может одновременно иметь как константные, так и неконстантные конструкторы.

Эта тема также обсуждалась в https://github.com/dart-lang/sdk/issues/36079 с некоторыми интересными комментариями.

Гюнтер Цохбауэр
источник
AFAIK const и final позволяют генерировать более оптимизированный JS.
Günter Zöchbauer
2
Они также полезны для значений по умолчанию в сигнатурах методов.
Флориан Лойч,
1
Может ли кто-нибудь объяснить мне, как работает эта линия? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas
Какая часть неясна? Это не похоже наconst
Günter Zöchbauer
3
const- хороший выигрыш в производительности для виджетов Flutter в соответствии с medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "Используйте const для создания ваших виджетов. Без const выборочного восстановления поддерева не происходит. Flutter создает новый экземпляр каждого виджет в поддереве и вызывает build (), тратя впустую драгоценные циклы, особенно если ваши методы сборки тяжелые. "
Дэвид Чендлер
8

Очень хорошо объяснено в деталях, но для пользователей, которые на самом деле ищут использование конструктора const

Он используется для увеличения производительности Flutter, так как помогает Flutter перестраивать только те виджеты, которые должны быть обновлены. При использовании setState () в StateFulWidgets будут перестраиваться только те компоненты, которые не являются конструктором const

Можно пояснить на примере->

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

Как в этом примере, должен быть изменен только текстовый заголовок, поэтому только этот виджет должен быть перестроен, поэтому создание всех других виджетов в качестве конструктора const поможет flutter сделать то же самое для повышения производительности.

Б.шрути
источник
0

Пример демонстрации того, что экземпляр const действительно определяет окончательное поле.
И в этом случае это невозможно предсказать во время компиляции.

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

Сейчас Дарт это проверит.

Анализ дротиков:

[dart] Невозможно определить конструктор 'const', потому что поле 'j' инициализировано непостоянным значением

Ошибка выполнения:

/main.dart ': error: строка 5 pos 17: выражение не является допустимой константой времени компиляции final int j = new DateTime.now (). millisecond;

Тикоре Ши
источник