Hàm tạo const thực sự hoạt động như thế nào?


111

Tôi nhận thấy có thể tạo một hàm tạo const trong Dart. Trong tài liệu, nó nói rằng consttừ đó được sử dụng để biểu thị một hằng số thời gian biên dịch.

Tôi đã tự hỏi điều gì sẽ xảy ra khi tôi sử dụng một consthàm tạo để tạo một đối tượng. Đây có phải là một đối tượng bất biến luôn giống nhau và có sẵn tại thời điểm biên dịch không? Khái niệm về hàm consttạo thực sự hoạt động như thế nào? Làm thế nào là một const constructor khác nhau từ một thường xuyên xây dựng?

Câu trả lời:


78

Hàm tạo Const tạo một thể hiện "chuẩn hóa".

Nghĩa là, tất cả các biểu thức hằng bắt đầu được chuẩn hóa, và sau đó các ký hiệu được "chuẩn hóa" này được sử dụng để nhận biết sự tương đương của các hằng số này.

Hợp thức hóa:

Quy trình chuyển đổi dữ liệu có nhiều hơn một biểu diễn có thể thành một biểu diễn chính tắc "chuẩn". Điều này có thể được thực hiện để so sánh các biểu diễn khác nhau về sự tương đương, đếm số lượng cấu trúc dữ liệu riêng biệt, để cải thiện hiệu quả của các thuật toán khác nhau bằng cách loại bỏ các phép tính lặp lại hoặc để có thể áp đặt một thứ tự sắp xếp có ý nghĩa.


Điều này có nghĩa là các biểu thức const như const Foo(1, 1)có thể đại diện cho bất kỳ hình thức hữu dụng nào hữu ích để so sánh trong máy ảo.

VM chỉ cần tính đến kiểu giá trị và các đối số theo thứ tự mà chúng xuất hiện trong biểu thức const này. Và, tất nhiên, chúng được giảm để tối ưu hóa.

Các hằng số có cùng giá trị được chuẩn hóa:

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

Các hằng số có các giá trị được chuẩn hóa khác nhau (vì các chữ ký khác nhau):

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

Hằng số không được tạo lại mỗi lần. Chúng được chuẩn hóa tại thời điểm biên dịch và được lưu trữ trong các bảng tra cứu đặc biệt (nơi chúng được băm bằng chữ ký chuẩn của chúng) để sau đó chúng được sử dụng lại.

PS

Biểu mẫu #Foo#int#1#int#1được sử dụng trong các mẫu này chỉ được sử dụng cho mục đích so sánh và nó không phải là hình thức chuẩn hóa (đại diện) thực sự trong Dart VM;

Nhưng biểu mẫu chuẩn hóa thực sự phải là biểu diễn chính tắc "chuẩn".


80

Tôi thấy câu trả lời của Lasse trên blog Chris Storms là một lời giải thích tuyệt vời.

Dart Constant Constructors

Tôi hy vọng họ không phiền vì tôi sao chép nội dung.

Đây là một lời giải thích tốt về các trường cuối cùng, nhưng nó không thực sự giải thích các hàm tạo const. Không có gì trong các ví dụ này thực sự sử dụng rằng các hàm tạo là các hàm tạo const. Bất kỳ lớp nào cũng có thể có các trường cuối cùng, các hàm tạo const hoặc không.

Một trường trong Dart thực sự là một vị trí lưu trữ ẩn danh kết hợp với getter và setter được tạo tự động để đọc và cập nhật bộ nhớ, đồng thời nó cũng có thể được khởi tạo trong danh sách bộ khởi tạo của phương thức khởi tạo.

Trường cuối cùng cũng vậy, chỉ cần không có bộ cài đặt, vì vậy cách duy nhất để đặt giá trị của nó là trong danh sách bộ khởi tạo phương thức khởi tạo, và không có cách nào để thay đổi giá trị sau đó - do đó là "cuối cùng".

Điểm của các hàm tạo const là không khởi tạo các trường cuối cùng, bất kỳ hàm tạo nào cũng có thể làm điều đó. Vấn đề là tạo các giá trị hằng số thời gian biên dịch: Các đối tượng trong đó tất cả các giá trị trường đã biết tại thời điểm biên dịch mà không cần thực hiện bất kỳ câu lệnh nào.

Điều đó đặt ra một số hạn chế đối với lớp và hàm tạo. Một phương thức khởi tạo const không được có phần thân (không có câu lệnh nào được thực thi!) Và lớp của nó không được có bất kỳ trường nào không phải là trường cuối cùng (giá trị mà chúng ta "biết" tại thời điểm biên dịch không thể thay đổi sau này). Danh sách trình khởi tạo cũng phải chỉ khởi tạo các trường thành các hằng số thời gian biên dịch khác, do đó, các cạnh bên phải được giới hạn trong "các biểu thức hằng số thời gian biên dịch" [1]. Và nó phải được bắt đầu bằng "const" - nếu không bạn chỉ nhận được một hàm tạo bình thường đáp ứng các yêu cầu đó. Điều đó hoàn toàn ổn, nó không phải là một hàm tạo const.

Để sử dụng một hàm tạo const để thực sự tạo một đối tượng hằng số thời gian biên dịch, sau đó bạn thay thế "new" bằng "const" trong một biểu thức-"mới". Bạn vẫn có thể sử dụng "new" với một const-constructor, và nó sẽ vẫn tạo một đối tượng, nhưng nó sẽ chỉ là một đối tượng mới bình thường, không phải là một giá trị hằng thời gian biên dịch. Nghĩa là: Một hàm tạo const cũng có thể được sử dụng như một hàm tạo bình thường để tạo các đối tượng trong thời gian chạy, cũng như tạo các đối tượng hằng số thời gian biên dịch tại thời điểm biên dịch.

Vì vậy, như một ví dụ:

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

Hằng số thời gian biên dịch được chuẩn hóa. Điều đó có nghĩa là cho dù bạn viết "const Point (0,0)" bao nhiêu lần, bạn chỉ tạo một đối tượng. Điều đó có thể hữu ích - nhưng không nhiều như bạn tưởng, vì bạn chỉ có thể tạo một biến const để giữ giá trị và sử dụng biến thay thế.

Vậy, hằng số thời gian biên dịch có ích gì?

  • Chúng hữu ích cho enums.
  • Bạn có thể sử dụng các giá trị hằng số thời gian biên dịch trong các trường hợp chuyển đổi.
  • Chúng được dùng làm chú thích.

Các hằng số thời gian biên dịch từng quan trọng hơn trước khi Dart chuyển sang khởi tạo biến một cách lười biếng. Trước đó, bạn chỉ có thể khai báo một biến toàn cục đã khởi tạo như "var x = foo;" nếu "foo" là hằng số thời gian biên dịch. Nếu không có yêu cầu đó, hầu hết các chương trình có thể được viết mà không cần sử dụng bất kỳ đối tượng const nào

Vì vậy, tóm tắt ngắn gọn: Các hàm tạo Const chỉ để tạo các giá trị hằng số thời gian biên dịch.

/ L

[1] Hay thực sự là: "Biểu thức hằng thời gian biên dịch tiềm năng" vì nó cũng có thể tham chiếu đến các tham số của hàm tạo. [2] Vì vậy, có, một lớp có thể có cả hàm tạo const và không phải const cùng một lúc.

Chủ đề này cũng đã được thảo luận trong https://github.com/dart-lang/sdk/issues/36079 với một số nhận xét thú vị.


AFAIK const và cuối cùng cho phép tạo JS tối ưu hơn.
Günter Zöchbauer

2
Chúng cũng hữu ích cho các giá trị mặc định trong chữ ký phương thức.
Florian Loitsch

1
Bất cứ ai có thể giải thích cho tôi rằng làm thế nào để dòng này hoạt động? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas

Phần nào chưa rõ? Nó không nhìn liên quan đếnconst
Günter Zöchbauer

3
constlà một chiến thắng hiệu suất tốt cho các widget Flutter theo medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "Sử dụng const để tạo các widget của bạn Nếu không có const, việc xây dựng lại cây con có chọn lọc sẽ không xảy ra. Flutter tạo một phiên bản mới của mỗi widget trong cây con và các lệnh gọi build () lãng phí các chu kỳ quý giá, đặc biệt nếu các phương thức xây dựng của bạn quá nặng. "
David Chandler

8

Được giải thích rất chi tiết nhưng đối với những người dùng thực sự đang tìm kiếm cách sử dụng một hàm tạo const

Nó được sử dụng để tăng hiệu suất Flutter vì nó giúp Flutter chỉ xây dựng lại các widget cần được cập nhật. Có nghĩa là trong khi Sử dụng setState () trong StateFulWidgets, chỉ những thành phần sẽ được xây dựng lại không phải là hàm tạo const

Có thể giải thích bằng ví dụ->

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

Như Trong ví dụ này, chỉ tiêu đề Văn bản mới được thay đổi, vì vậy chỉ tiện ích con này mới được xây dựng lại, do đó, việc đặt tất cả các tiện ích con khác dưới dạng hàm tạo const sẽ giúp làm tương tự để tăng hiệu suất.


0

Một ví dụ demo mà ví dụ const thực sự quyết định theo trường cuối cùng.
Và trong trường hợp này, nó không thể được dự đoán trong thời gian biên dịch.

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

Bây giờ phi tiêu sẽ kiểm tra nó.

Phân tích Dart:

[dart] Không thể xác định hàm tạo 'const' vì trường 'j' được khởi tạo với giá trị không phải là hằng số

Lỗi runtime:

/main.dart ': error: dòng 5 pos 17: biểu thức không phải là hằng số thời gian biên dịch hợp lệ end int j = new DateTime.now (). mili giây;

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.