Як насправді працює конструктор const?


112

Я помітив, що в Dart можна створити конструктор const. У документації сказано, що constслово використовується для позначення чогось константи часу компіляції.

Мені було цікаво, що відбувається, коли я використовую constконструктор для створення об'єкта. Це як незмінний об'єкт, який завжди однаковий і доступний під час компіляції? Як насправді працює концепція constконструктора? Чим конструктор const відрізняється від звичайного конструктора?

Відповіді:


78

Конструктор Const створює "канонізований" екземпляр.

Тобто всі постійні вирази починаються канонізованими, а пізніше ці "канонізовані" символи використовуються для визнання еквівалентності цих констант.

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

Процес перетворення даних, що має більше одного можливого подання, у "стандартне" канонічне подання. Це можна зробити для порівняння різних уявлень на еквівалентність, підрахунку кількості різних структур даних, підвищення ефективності різних алгоритмів шляхом усунення повторних обчислень або для надання можливості накласти змістовний порядок сортування.


Це означає, що вирази const типу const Foo(1, 1)можуть представляти будь-яку корисну форму, корисну для порівняння у віртуальній машині.

ВМ потрібно лише враховувати тип значення та аргументи в тому порядку, в якому вони трапляються в цьому виразі 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

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

PS

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

Але справжньою формою канонізації має бути "стандартне" канонічне подання.


80

Я вважаю відповідь Лассе в блозі Кріса Стормса чудовим поясненням.

Конструктори Dart постійні

Сподіваюся, вони не заперечують, що я копіюю вміст.

Це чітке пояснення підсумкових полів, але це насправді не пояснює конструкторів const. Ніщо в цих прикладах насправді не використовує те, що конструктори є конструкторами const. Будь-який клас може мати остаточні поля, конструктори const чи ні.

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

Заключне поле те саме, лише без сеттера, тому єдиний спосіб встановити його значення знаходиться в списку ініціалізатора конструктора, і немає можливості змінити значення після цього - отже, "остаточне".

Сенс конструкторів const не в ініціалізації кінцевих полів, будь-який генеративний конструктор може це зробити. Сенс полягає у створенні постійних значень часу компіляції: Об'єкти, де всі значення поля відомі вже під час компіляції, не виконуючи жодних операторів.

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

Щоб використовувати конструктор const для фактичного створення константного об'єкта часу компіляції, ви замінюєте "new" на "const" на "new" -вираз. Ви все ще можете використовувати "new" з const-конструктором, і він все одно створить об'єкт, але це буде просто нормальний новий об'єкт, а не постійне значення часу компіляції. Тобто: Конструктор 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, щоб утримувати значення і використовувати замість нього змінну.

Отже, для чого потрібні постійні час компіляції?

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

Константи часу компіляції раніше були важливішими, перш ніж Dart перейшов на ліниво ініціалізацію змінних. Перед цим ви могли лише оголосити ініціалізовану глобальну змінну на зразок "var x = foo;" якщо "foo" була константа часу компіляції. Без цієї вимоги більшість програм можна писати без використання об'єктів const

Отже, короткий підсумок: Конструктори Const призначені лише для створення постійних значень часу компіляції.

/ Л

[1] Або дійсно: "Потенційно виражені постійні вирази часу", оскільки вони також можуть посилатися на параметри конструктора. [2] Отже, так, клас може мати одночасно конструктори const і non-const.

Цю тему також обговорювали на https://github.com/dart-lang/sdk/isissue/36079 з цікавими коментарями.


Константа та остаточний результат AFAIK дозволяють генерувати більш оптимізовані JS.
Günter Zöchbauer

2
Вони також корисні для значень за замовчуванням у підписах методів.
Флоріан Лойч

1
Хтось може мені пояснити, як працює ця лінія? Point.clone(Point other): x = other.x, y = other.y;
Дакш Гаргас

Яка частина незрозуміла? Це не схоже на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 допоможе махати зробити те ж саме для підвищення продуктивності.


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 ': помилка: рядок 5 поз 17: вираз не є дійсною константа часу компіляції підсумковий int j = новий DateTime.now (). мілісекунда;

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.