const 构造函数实际上是如何工作的?

Posted

技术标签:

【中文标题】const 构造函数实际上是如何工作的?【英文标题】:How does the const constructor actually work? 【发布时间】:2014-03-11 18:24:21 【问题描述】:

我注意到可以在 Dart 中创建一个 const 构造函数。在文档中,它说const 一词用于表示编译时间常数。

我想知道当我使用const 构造函数创建对象时会发生什么。这是否像一个在编译时始终相同且可用的不可变对象? const 构造函数的概念实际上是如何工作的? const 构造函数与 常规 构造函数有何不同?

【问题讨论】:

【参考方案1】:

Const 构造函数创建一个“规范化”实例。

也就是说,所有常量表达式都开始规范化,然后这些“规范化”符号用于识别这些常量的等价性。

规范化:

将具有多种可能表示形式的数据转换为“标准”规范表示的过程。这可以用来比较不同表示的等价性,计算不同数据结构的数量,通过消除重复计算来提高各种算法的效率,或者可以施加有意义的排序顺序。


这意味着像 const Foo(1, 1) 这样的 const 表达式可以表示任何可用于在虚拟机中进行比较的可用形式。

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, 2), "hello"); // $Baz$Foo$int$1$int$2$String$hello

不会每次都重新创建常量。它们在编译时被规范化并存储在特殊的查找表中(在那里它们通过它们的规范签名进行散列),以后可以从中重用。

附言

这些示例中使用的#Foo#int#1#int#1 形式仅用于比较目的,它不是 Dart VM 中的规范化(表示)的真实形式;

但真正的规范化形式必须是“标准”的规范表示。

【讨论】:

为什么baz1baz2 被视为具有不同规范化值的常量?是不是笔误? @ArtificialStupidity 谢谢!【参考方案2】:

我发现 Lasse 在 Chris Storms 博客上的回答是一个很好的解释。

Dart Constant Constructors

我希望他们不介意我复制内容。

这是对 final 字段的一个很好的解释,但实际上并不是 解释 const 构造函数。这些示例中的任何内容都没有实际使用 构造函数是 const 构造函数。任何类都可以有final 字段,常量构造函数与否。

Dart 中的字段实际上是一个匿名存储位置,结合了 一个自动创建的 getter 和 setter,用于读取和更新 存储,也可以在构造函数的初始化器中初始化 列表。

final 字段是相同的,只是没有设置器,所以唯一的方法是 设置它的值在构造函数初始化列表中,并且没有 之后更改值的方法 - 因此是“最终”。

const 构造函数的重点不是初始化 final 字段,任何 生成式构造函数可以做到这一点。重点是创造 编译时常量值:所有字段值所在的对象 在编译时已知,无需执行任何语句。

这对类和构造函数施加了一些限制。一个常量 构造函数不能有主体(不执行语句!)及其类 不能有任何非最终字段(我们在编译时“知道”的值 时间以后一定不能改变)。初始化器列表还必须 仅将字段初始化为其他编译时常量,因此 右侧仅限于“编译时常数 表达式“[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 变量来保存该值并使用该变量 而是。

那么,编译时常量到底有什么用处?

它们对枚举很有用。 您可以在切换情况下使用编译时常量值。 它们用作注释。

在 Dart 切换之前,编译时常量曾经更重要 懒惰地初始化变量。在此之前,您只能声明 一个已初始化的全局变量,例如“var x = foo;”如果“foo”是 编译时常量。如果没有这个要求,大多数程序都可以 不使用任何 const 对象编写

所以,简短的总结:Const 构造函数仅用于创建 编译时常量值。

/L

[1] 或者说真的:“潜在的编译时常量表达式” 因为它也可能引用构造函数参数。 [2] 是的,一个类可以同时拥有 const 和 non-const 构造函数。

https://github.com/dart-lang/sdk/issues/36079 中也有一些有趣的 cmets 讨论了这个话题。

【讨论】:

AFAIK const 和 final 允许生成更优化的 JS。 它们对于方法签名中的默认值也很有用。 谁能向我解释这条线是如何工作的? Point.clone(Point other): x = other.x, y = other.y; const 表示编译时间常数,因此无法修改。编译时间常数被规范化。每个具有相同构造函数参数的const Point(0,0) 都将返回相同的规范化实例。 const 根据medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1 为 Flutter 小部件带来了不错的性能优势“使用 const 构建您的小部件没有 const,不会发生子树的选择性重建。Flutter 创建一个新实例子树中的每个小部件并调用 build() 会浪费宝贵的周期,尤其是在您的构建方法很繁重的情况下。”【参考方案3】:

const 实例真正由 final 字段决定的示例演示。 在这种情况下,它无法在编译时预测。

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': error: line 5 pos 17: expression is not a valid compile-time constant final int j = new DateTime.now().millisecond;

【讨论】:

【参考方案4】:

非常详细的解释,但对于那些真正正在寻找使用 const 构造函数的用户

它用于提高 Flutter 性能,因为它有助于 Flutter 仅重建应更新的小部件。使用时的方法 StateFulWidgets 中的 setState(),只有那些组件会被重建 非 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');
        ,
      ),
    );
  

在这个例子中,只有 Text 标题应该被改变,所以只有这个小部件应该被重建,所以将所有其他小部件设置为 const 构造函数将有助于 Flutter 做同样的事情以提高性能。

【讨论】:

final 变量也能提高性能吗? 那为什么不自动使用呢?不需要使用 const 关键字,编译器会为我们做这件事。 有人看过这方面的性能测试吗?我看到很多你应该使用const constructor 的声明,解释似乎是有道理的,但没有证据。【参考方案5】:

在本视频中,您将了解我们为什么需要它。 https://www.youtube.com/watch?v=B1fIqdqwWw8&t=558s09:1816:09


在文档中:https://dart.dev/guides/language/language-tour

常量构造函数。使用 常量构造函数,将 const 关键字放在构造函数之前 名称:

> var p = const ImmutablePoint(2, 2);

构造两个相同的 编译时常量会产生一个单一的规范实例:

 var a = const ImmutablePoint(1, 1);
 var b = const ImmutablePoint(1,1);
   
 assert(identical(a, b)); // They are the same instance!

在一个 常量上下文,您可以在构造函数之前省略 const 或 文字。例如,看看这段代码,它创建了一个 const 映射:

 // Lots of const keywords here.
 const pointAndLine = const   
   'point': const [const ImmutablePoint(0, 0)],
   'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
 ;

你可以省略 除了第一次使用 const 关键字:

 // Only one const, which establishes the constant context.
 const pointAndLine =    
    'point': [ImmutablePoint(0, 0)],
    'line':  [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)], ;

如果一个常数 构造函数在常量上下文之外并且在没有的情况下被调用 const,它创建一个非常量对象:

> var a = const ImmutablePoint(1, 1); // Creates a constant var b =
> ImmutablePoint(1, 1); // Does NOT create a constant
> 
> assert(!identical(a, b));// NOT the same instance!

有关更多信息,您可以查看以下 2 个答案:

1-https://***.com/a/21746692/14409491

2-https://***.com/a/21745617/14409491

【讨论】:

以上是关于const 构造函数实际上是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

复制“const”构造函数 - 链接器错误?

拷贝构造函数和const成员函数

Dart 工厂构造函数 - 它与“const”构造函数有何不同

如何在 C++ 子类的构造函数中初始化超类的 const 成员变量?