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


111

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

我想知道当我使用const构造函数创建对象时会发生什么。这就像一个不变的对象,在编译时总是一样并且可用吗?const构造函数的概念实际上如何工作?如何是一个常量构造从不同的规则构造?

Answers:


78

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

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

规范化:

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


这意味着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中规范化(表示)的真实形式。

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


80

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

飞镖常量构造函数

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

这是对final字段的很好的解释,但是并没有真正解释const构造函数。这些示例中没有任何东西实际使用构造函数为const构造函数。任何类都可以具有final字段,也可以具有const构造函数。

Dart中的字段实际上是一个匿名存储位置,它与自动创建的读取和更新存储的getter和setter相结合,还可以在构造函数的初始化程序列表中对其进行初始化。

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

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

这对类和构造函数施加了一些限制。const构造函数不能具有主体(不执行任何语句!),并且其类不得具有任何非最终字段(在编译时我们“知道”的值以后不能更改)。初始化程序列表还必须仅将字段初始化为其他编译时常量,因此右侧仅限于“编译时常量表达式” [1]。并且必须以“ const”作为前缀-否则,您将得到一个满足这些要求的普通构造函数。很好,这不是const构造函数。

为了使用const构造函数实际创建一个编译时常量对象,然后在“ new”表达式中将“ new”替换为“ const”。您仍然可以将“ 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/issues/36079中也讨论了该主题,并提供了一些有趣的评论。


AFAIK const和final允许生成更多优化的JS。
君特Zöchbauer

2
它们对于方法签名中的默认值也很有用。
Florian Loitsch 2014年

1
谁能告诉我这条线是如何工作的?Point.clone(Point other): x = other.x, y = other.y;
Daksh Gargas

哪一部分不清楚?它看起来并不相关const
君特Zöchbauer

3
const根据medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbebb1,Flutter小部件获得了不错的性能胜利 “使用const来构建小部件如果没有const,则不会发生子树的选择性重建。Flutter会为每个子树创建一个新实例子树中的窗口小部件并调用build()浪费了宝贵的周期,尤其是在您的构建方法繁重的情况下。”
大卫·钱德勒

8

非常详细的解释,但对于实际上正在寻找使用const构造函数的用户而言

它用于提高Flutter的性能,因为它有助于Flutter仅重建应更新的小部件。意味着在StateFulWidgets中使用setState()时,仅那些非const构造函数的组件将被重建。

可以用example->来解释

    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构造函数将有助于Flutter做同样的事情以提高性能。


0

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':错误:第5行pos 17:表达式不是有效的编译时常量final int j = new DateTime.now()。millisecond;

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.