Bagaimana sebenarnya konstruktor const bekerja?

112

Saya telah memperhatikan bahwa mungkin untuk membuat konstruktor const di Dart. Dalam dokumentasi, dikatakan bahwa constkata digunakan untuk menunjukkan sesuatu yang konstan waktu kompilasi.

Saya bertanya-tanya apa yang terjadi ketika saya menggunakan constkonstruktor untuk membuat objek. Apakah ini seperti objek yang tidak dapat diubah yang selalu sama dan tersedia pada waktu kompilasi? Bagaimana sebenarnya konsep constkonstruktor bekerja? Apa perbedaan konstruktor const dari konstruktor biasa ?

markovuksanovic
sumber

Jawaban:

78

Konstruktor Const membuat instance "dikanonikalisasi".

Artinya, semua ekspresi konstan mulai dikanonikalisasi, dan kemudian simbol "dikanonikalisasi" ini digunakan untuk mengenali kesetaraan dari konstanta ini.

Kanonikalisasi:

Proses untuk mengonversi data yang memiliki lebih dari satu kemungkinan representasi menjadi representasi kanonik "standar". Ini dapat dilakukan untuk membandingkan representasi yang berbeda untuk kesetaraan, untuk menghitung jumlah struktur data yang berbeda, untuk meningkatkan efisiensi berbagai algoritma dengan menghilangkan penghitungan berulang, atau untuk memungkinkan penerapan urutan pengurutan yang bermakna.


Ini berarti bahwa ekspresi const like const Foo(1, 1)dapat mewakili bentuk apa pun yang dapat digunakan yang berguna untuk perbandingan di mesin virtual.

VM hanya perlu mempertimbangkan jenis nilai dan argumen dalam urutan kemunculannya dalam ekspresi const ini. Dan, tentu saja, mereka dikurangi untuk pengoptimalan.

Konstanta dengan nilai dikanonikalisasi yang sama:

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

Konstanta dengan nilai dikanonikalisasi yang berbeda (karena tanda tangan berbeda):

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

Konstanta tidak dibuat ulang setiap kali. Mereka dikanonikalisasi pada waktu kompilasi dan disimpan dalam tabel pencarian khusus (di mana mereka di-hash oleh tanda tangan kanonik mereka) dari mana mereka kemudian digunakan kembali.

PS

Formulir yang #Foo#int#1#int#1digunakan dalam sampel ini hanya digunakan untuk tujuan perbandingan dan bukan bentuk nyata kanonikalisasi (representasi) di VM Dart;

Tetapi bentuk kanonikalisasi yang sebenarnya harus berupa representasi kanonik "standar".

mezoni
sumber
81

Saya menemukan jawaban Lasse di blog Chris Storms sebagai penjelasan yang bagus.

Konstruktor Konstan Dart

Saya harap mereka tidak keberatan saya menyalin konten tersebut.

Ini adalah penjelasan yang bagus tentang bidang akhir, tetapi tidak benar-benar menjelaskan konstruktor const. Tidak ada dalam contoh ini yang benar-benar menggunakan bahwa konstruktor adalah konstruktor const. Setiap kelas dapat memiliki bidang akhir, konstruktor konstanta atau tidak.

Bidang di Dart sebenarnya adalah lokasi penyimpanan anonim yang digabungkan dengan pengambil dan penyetel yang dibuat secara otomatis yang membaca dan memperbarui penyimpanan, dan juga dapat diinisialisasi dalam daftar penginisialisasi konstruktor.

Bidang terakhir adalah sama, hanya tanpa penyetel, jadi satu-satunya cara untuk menyetel nilainya adalah dalam daftar penginisialisasi konstruktor, dan tidak ada cara untuk mengubah nilai setelah itu - karenanya "final".

Inti dari konstruktor const bukanlah untuk menginisialisasi bidang final, konstruktor generatif mana pun dapat melakukannya. Intinya adalah membuat nilai konstanta waktu kompilasi: Objek yang semua nilai bidangnya sudah diketahui pada waktu kompilasi, tanpa menjalankan pernyataan apa pun.

Itu menempatkan beberapa batasan pada kelas dan konstruktor. Konstruktor const tidak boleh memiliki isi (tidak ada pernyataan yang dieksekusi!) Dan kelasnya tidak boleh memiliki bidang non-final (nilai yang kita "ketahui" pada waktu kompilasi tidak boleh diubah nanti). Daftar penginisialisasi juga hanya harus menginisialisasi kolom ke konstanta waktu kompilasi lainnya, jadi sisi kanan dibatasi ke "ekspresi konstanta waktu kompilasi" [1]. Dan itu harus diawali dengan "const" - jika tidak, Anda akan mendapatkan konstruktor normal yang kebetulan memenuhi persyaratan tersebut. Itu baik-baik saja, itu bukan konstruktor const.

Untuk menggunakan konstruktor const untuk benar-benar membuat objek konstanta waktu kompilasi, Anda kemudian mengganti "baru" dengan "const" dalam ekspresi "baru". Anda masih dapat menggunakan "new" dengan konstruktor-konst, dan itu masih akan membuat sebuah objek, tetapi itu hanya akan menjadi objek baru yang normal, bukan nilai konstanta waktu kompilasi. Yaitu: Konstruktor konst juga dapat digunakan sebagai konstruktor normal untuk membuat objek pada waktu proses, serta membuat objek konstan waktu kompilasi pada waktu kompilasi.

Jadi, sebagai contoh:

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

Konstanta waktu kompilasi dikanonikalisasi. Artinya, tidak peduli berapa kali Anda menulis "const Point (0,0)", Anda hanya membuat satu objek. Itu mungkin berguna - tetapi tidak sebanyak yang terlihat, karena Anda bisa membuat variabel const untuk menampung nilai dan menggunakan variabel sebagai gantinya.

Jadi, apa gunanya konstanta waktu kompilasi?

  • Mereka berguna untuk enum.
  • Anda dapat menggunakan nilai konstanta waktu kompilasi dalam kasus sakelar.
  • Mereka digunakan sebagai anotasi.

Konstanta waktu kompilasi dulu lebih penting sebelum Dart beralih ke variabel inisialisasi yang lambat. Sebelumnya, Anda hanya dapat mendeklarasikan variabel global yang diinisialisasi seperti "var x = foo;" jika "foo" adalah konstanta waktu kompilasi. Tanpa persyaratan itu, sebagian besar program dapat ditulis tanpa menggunakan objek const

Jadi, ringkasan singkat: Konstruktor konst hanya untuk membuat nilai konstanta waktu kompilasi.

/ L

[1] Atau sebenarnya: "Kemungkinan ekspresi konstanta waktu kompilasi" karena mungkin juga merujuk ke parameter konstruktor. [2] Jadi ya, sebuah kelas dapat memiliki konstruktor const dan non-const pada saat yang bersamaan.

Topik ini juga pernah dibahas di https://github.com/dart-lang/sdk/issues/36079 dengan beberapa komentar menarik.

Günter Zöchbauer
sumber
Const dan final AFAIK memungkinkan untuk menghasilkan JS yang lebih optimal.
Günter Zöchbauer
2
Mereka juga berguna untuk nilai default di tanda tangan metode.
Florian Loitsch
1
Adakah yang bisa menjelaskan kepada saya bahwa bagaimana cara kerja baris ini? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas
Bagian mana yang tidak jelas? Sepertinya tidak ada hubungannya denganconst
Günter Zöchbauer
3
constadalah kemenangan kinerja yang bagus untuk widget Flutter menurut medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "Gunakan const untuk membuat widget Anda Tanpa konst, pembangunan kembali sub-hierarki secara selektif tidak terjadi. Flutter membuat instance baru dari setiap widget widget di sub-pohon dan memanggil build () yang menyia-nyiakan siklus yang berharga terutama jika metode build Anda berat. "
David Chandler
8

Dijelaskan dengan sangat baik secara detail tetapi untuk pengguna yang benar-benar mencari penggunaan konstruktor const

Ini digunakan untuk Meningkatkan Kinerja Flutter karena membantu Flutter untuk membangun kembali hanya widget yang harus diperbarui. Berarti saat Menggunakan setState () di StateFulWidgets, hanya komponen yang akan dibangun kembali yang bukan konstruktor konstan

Dapat dijelaskan dengan contoh->

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

Seperti dalam contoh ini, hanya judul Teks yang harus diubah, jadi hanya widget ini yang harus dibangun kembali, jadi menjadikan semua widget lain sebagai konstruktor const akan membantu flutter melakukan hal yang sama untuk meningkatkan kinerja.

B.shruti
sumber
0

Contoh demo yang benar-benar ditentukan oleh instance const oleh bidang terakhir.
Dan dalam hal ini, tidak dapat diprediksi dalam waktu kompilasi.

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

Sekarang anak panah akan memeriksanya.

Analisis Dart:

[dart] Tidak dapat mendefinisikan konstruktor 'const' karena bidang 'j' diinisialisasi dengan nilai non-konstan

Runtime Error:

/main.dart ': error: baris 5 pos 17: ekspresi bukan konstanta waktu kompilasi yang valid akhir int j = new DateTime.now (). milidetik;

Ticore Shih
sumber