ตัวสร้าง const ทำงานอย่างไร?


113

ฉันสังเกตเห็นว่าเป็นไปได้ที่จะสร้างตัวสร้าง const ใน Dart ในเอกสารระบุว่าconstคำนั้นใช้เพื่อแสดงค่าคงที่ของเวลาในการคอมไพล์

ฉันสงสัยว่าจะเกิดอะไรขึ้นเมื่อฉันใช้ตัวconstสร้างเพื่อสร้างวัตถุ นี่เป็นเหมือนวัตถุที่ไม่เปลี่ยนรูปซึ่งมักจะเหมือนกันและพร้อมใช้งานในเวลาคอมไพล์หรือไม่? แนวคิดของคอนconstสตรัคเตอร์ทำงานอย่างไร? คอนสตรัคเตอร์constแตกต่างจากคอนสตรัคเตอร์ทั่วไปอย่างไร?

คำตอบ:


85

ตัวสร้าง Const สร้างอินสแตนซ์ "canonicalized"

นั่นคือนิพจน์คงที่ทั้งหมดจะเริ่มต้นในรูปแบบบัญญัติและต่อมาสัญลักษณ์ "บัญญัติ" เหล่านี้จะถูกใช้เพื่อรับรู้ความเท่าเทียมกันของค่าคงที่เหล่านี้

การบัญญัติศัพท์:

กระบวนการในการแปลงข้อมูลที่มีการแทนค่าที่เป็นไปได้มากกว่าหนึ่งรายการเป็นการแสดงตามรูปแบบบัญญัติ "มาตรฐาน" สิ่งนี้สามารถทำได้เพื่อเปรียบเทียบการแทนค่าต่างๆสำหรับการเทียบเคียงเพื่อนับจำนวนโครงสร้างข้อมูลที่แตกต่างกันเพื่อปรับปรุงประสิทธิภาพของอัลกอริทึมต่างๆโดยการกำจัดการคำนวณซ้ำ ๆ หรือเพื่อให้สามารถกำหนดลำดับการเรียงลำดับที่มีความหมายได้


ซึ่งหมายความว่านิพจน์ 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

ค่าคงที่จะไม่ถูกสร้างขึ้นใหม่ในแต่ละครั้ง พวกเขาได้รับการบัญญัติในเวลาคอมไพล์และจัดเก็บไว้ในตารางการค้นหาพิเศษ (ซึ่งถูกแฮชด้วยลายเซ็นที่เป็นที่ยอมรับ) ซึ่งจะถูกนำมาใช้ใหม่ในภายหลัง

ปล

แบบฟอร์มที่#Foo#int#1#int#1ใช้ในตัวอย่างเหล่านี้ใช้เพื่อวัตถุประสงค์ในการเปรียบเทียบเท่านั้นและไม่ใช่รูปแบบที่แท้จริงของการบัญญัติ (การแสดง) ใน Dart VM

แต่รูปแบบการบัญญัติศัพท์ที่แท้จริงต้องเป็น "มาตรฐาน" การแสดงตามรูปแบบบัญญัติ


83

ฉันพบคำตอบของ Lasse ในบล็อก Chris Storms เป็นคำอธิบายที่ดี

Dart Constant Constructor

ฉันหวังว่าพวกเขาจะไม่รังเกียจที่ฉันคัดลอกเนื้อหา

นี่เป็นคำอธิบายที่ดีของฟิลด์สุดท้าย แต่ไม่ได้อธิบายถึงตัวสร้าง const จริงๆ ไม่มีสิ่งใดในตัวอย่างเหล่านี้ที่ใช้ว่าตัวสร้างเป็นตัวสร้าง const ทุกชั้นสามารถมีฟิลด์สุดท้ายตัวสร้าง const หรือไม่ก็ได้

ฟิลด์ใน Dart เป็นที่เก็บข้อมูลแบบไม่ระบุตัวตนรวมกับ getter และ setter ที่สร้างขึ้นโดยอัตโนมัติซึ่งอ่านและอัปเดตที่จัดเก็บข้อมูลและยังสามารถเริ่มต้นในรายการ initializer ของตัวสร้าง

ฟิลด์สุดท้ายจะเหมือนกันโดยไม่มีตัวเซ็ตดังนั้นวิธีเดียวที่จะตั้งค่าได้คือในรายการตัวสร้างตัวเริ่มต้นและไม่มีวิธีใดที่จะเปลี่ยนค่าหลังจากนั้นจึงเป็น "ขั้นสุดท้าย"

จุดของตัวสร้าง const ไม่ใช่การเริ่มต้นฟิลด์สุดท้ายตัวสร้างกำเนิดใด ๆ สามารถทำได้ ประเด็นคือการสร้างค่าคงที่เวลาคอมไพล์: ออบเจ็กต์ที่ทราบค่าฟิลด์ทั้งหมดแล้วในเวลาคอมไพล์โดยไม่ต้องดำเนินการคำสั่งใด ๆ

นั่นทำให้มีข้อ จำกัด บางประการเกี่ยวกับคลาสและคอนสตรัคเตอร์ ตัวสร้าง const ไม่สามารถมีเนื้อความได้ (ไม่มีคำสั่งดำเนินการ!) และคลาสของมันต้องไม่มีฟิลด์ที่ไม่ใช่ขั้นสุดท้าย (ค่าที่เรา "รู้" ในเวลาคอมไพล์ต้องไม่สามารถเปลี่ยนแปลงได้ในภายหลัง) รายการตัวเริ่มต้นจะต้องกำหนดค่าเริ่มต้นฟิลด์ให้เป็นค่าคงที่เวลาคอมไพล์อื่น ๆ เท่านั้นดังนั้นด้านขวามือจึง จำกัด ไว้ที่ "นิพจน์ค่าคงที่เวลาคอมไพล์" [1] และต้องขึ้นต้นด้วย "const" มิฉะนั้นคุณจะได้ตัวสร้างปกติที่ตอบสนองความต้องการเหล่านั้น นั่นเป็นเรื่องที่ดีไม่ใช่แค่ตัวสร้าง const

ในการใช้คอนสตรัคเตอร์ const เพื่อสร้างอ็อบเจ็กต์ค่าคงที่เวลาคอมไพล์จริงคุณจึงแทนที่ "new" ด้วย "const" ในนิพจน์ "ใหม่" คุณยังคงสามารถใช้ "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 เพื่อเก็บค่าและใช้ตัวแปรแทนได้

ดังนั้นค่าคงที่เวลาคอมไพล์คืออะไรที่ดีสำหรับต่อไป?

  • มีประโยชน์สำหรับ enums
  • คุณสามารถใช้ค่าคงที่เวลาคอมไพล์ในกรณีสวิตช์
  • ใช้เป็นคำอธิบายประกอบ

ค่าคงที่เวลาคอมไพล์เคยมีความสำคัญมากกว่าก่อนที่ Dart จะเปลี่ยนไปใช้ตัวแปรเริ่มต้นอย่างเฉื่อยชา ก่อนหน้านั้นคุณสามารถประกาศตัวแปรโกลบอลที่เริ่มต้นเท่านั้นเช่น "var x = foo;" ถ้า "foo" เป็นค่าคงที่เวลาคอมไพล์ หากไม่มีข้อกำหนดนั้นโปรแกรมส่วนใหญ่สามารถเขียนได้โดยไม่ต้องใช้วัตถุ const ใด ๆ

สรุปสั้น ๆ : คอนสตรัคเตอร์ Const เป็นเพียงการสร้างค่าคงที่ของเวลาคอมไพล์

/ ล

[1] หรือจริงๆ: "นิพจน์คงเวลาคอมไพล์ที่เป็นไปได้" เนื่องจากอาจอ้างถึงพารามิเตอร์ตัวสร้างด้วย [2] ใช่แล้วคลาสสามารถมีทั้งตัวสร้าง const และ non-const ในเวลาเดียวกัน

หัวข้อนี้ยังมีการพูดคุยในhttps://github.com/dart-lang/sdk/issues/36079พร้อมกับความคิดเห็นที่น่าสนใจ


AFAIK const และสุดท้ายอนุญาตให้สร้าง JS ที่เหมาะสมยิ่งขึ้น
GünterZöchbauer

2
นอกจากนี้ยังมีประโยชน์สำหรับค่าเริ่มต้นในลายเซ็นของวิธีการ
Florian Loitsch

1
ใครช่วยอธิบายให้ฉันฟังหน่อยว่าสายงานนี้ทำงานอย่างไร? Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas

ส่วนใดไม่ชัดเจน ดูไม่เกี่ยวข้องกับconst
GünterZöchbauer

3
constเป็นผลงานที่ดีสำหรับวิดเจ็ต Flutter ตามmedium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 "ใช้ const เพื่อสร้างวิดเจ็ตของคุณหากไม่มี const การสร้างแผนผังย่อยใหม่จะไม่เกิดขึ้น Flutter สร้างอินสแตนซ์ใหม่ของแต่ละรายการ วิดเจ็ตในแผนผังย่อยและการเรียก build () ทำให้สิ้นเปลืองวัฏจักรอันมีค่าโดยเฉพาะอย่างยิ่งหากวิธีการสร้างของคุณมีน้ำหนักมาก "
David Chandler

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 (). มิลลิวินาที;

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.