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

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

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

Ответ 1

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

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

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

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


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

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

Константы с теми же каноническими значениями:

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

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

P.S.

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

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

Ответ 2

Я нашел ответ Лассе в блоге Криса Стормса отличным объяснением.

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

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

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

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

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

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

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

Чтобы использовать конструктор const для фактического создания объекта константы во время компиляции, вы затем заменяете "new" на "const" в "new" -expression. Вы все еще можете использовать "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)", вы создаете только один объект. Это может быть полезно - но не так сильно, как может показаться, поскольку вы можете просто создать переменную const для хранения значения и использовать ее вместо этого.

Итак, чем же хороши константы времени компиляции?

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

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

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

/L

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

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

Ответ 3

Пример демонстрации, который экземпляр 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)
  });
}

Забастовкa >

Теперь дротик проверит его.

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

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

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

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

Ответ 4

Можно ли это понимать как макроопределение?