constコンストラクタは実際にどのように機能しますか?


111

Dartでconstコンストラクターを作成できることに気づきました。ドキュメントではconst、コンパイル時定数を表すために単語が使用されていると書かれています。

constオブジェクトを作成するためにコンストラクターを使用するとどうなるかと思っていました。これは常に同じで、コンパイル時に使用できる不変オブジェクトのようなものですか?constコンストラクタの概念は実際にはどのように機能しますか?どのようにあるのconstコンストラクタは異なる通常のコンストラクタ?

回答:


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

定数は毎回再作成されません。それらはコンパイル時に正規化され、後で再利用される特別なルックアップテーブル(正規署名によってハッシュされる)に格納されます。

PS

フォーム#Foo#int#1#int#1これらのサンプルで使用されるのみ比較目的のために使用され、これは、DART VMにおける正規化(表現)の実際の形はありません。

ただし、実際の正規化形式は、「標準」の正規表現でなければなりません。


80

私はクリス・ストームスのブログでラッセの答えが素晴らしい説明だと思います。

Dart定数コンストラクター

私がコンテンツをコピーすることを彼らが気にしないことを望みます。

これはfinalフィールドの詳細な説明ですが、constコンストラクターを実際には説明していません。これらの例では、コンストラクターがconstコンストラクターであることを実際に使用するものはありません。どのクラスにもfinalフィールド、constコンストラクターを含めることができます。

Dartのフィールドは、実際には匿名のストレージの場所であり、ストレージを読み取って更新する自動作成されたゲッターとセッターと組み合わされており、コンストラクターの初期化リストで初期化することもできます。

最終フィールドは同じですが、セッターがありません。そのため、その値を設定する唯一の方法は、コンストラクター初期化子リスト内にあり、その後に値を変更する方法はないため、「最終」となります。

constコンストラクターの目的は、最終フィールドを初期化することではありません。生成コンストラクターはそれを行うことができます。ポイントは、コンパイル時の定数値を作成することです。ステートメントを実行せずに、すべてのフィールド値がコンパイル時に既知であるオブジェクト。

これは、クラスとコンストラクターにいくつかの制限を課します。constコンストラクターには本体(ステートメントは実行されません!)を含めることはできず、そのクラスにはfinal以外のフィールドがあってはなりません(コンパイル時に「知っている」値は後で変更できません)。イニシャライザリストは、フィールドを他のコンパイル時定数に初期化するだけでもよいため、右側は「コンパイル時定数式」[1]に限定されます。そして、 "const"を前に付ける必要があります-そうでない場合は、これらの要件を満たす偶然の通常のコンストラクタを取得します。それはまったく問題ありません。単にconstコンストラクタではありません。

constコンストラクタを使用してコンパイル時の定数オブジェクトを実際に作成するには、「new」式の「new」を「const」に置き換えます。const-constructorで「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)」を何回書き込んでも、1つのオブジェクトしか作成されません。これは便利かもしれませんが、const変数を作成して値を保持し、代わりにその変数を使用できるため、思ったほどではありません。

それで、とにかくコンパイル時定数は何に適していますか?

  • 列挙型に役立ちます。
  • スイッチケースではコンパイル時の定数値を使用できます。
  • それらは注釈として使用されます。

Dartが遅延初期化変数に切り替える前は、コンパイル時定数がより重要でした。その前は、 "var x = foo;"のような初期化されたグローバル変数しか宣言できませんでした。「foo」がコンパイル時の定数である場合。その要件がなければ、ほとんどのプログラムはconstオブジェクトを使用せずに作成できます

つまり、要約すると、Constコンストラクターはコンパイル時の定数値を作成するためだけのものです。

/ L

[1]または本当に:「潜在的にコンパイル時の定数式」。これは、コンストラクターパラメーターも参照する場合があるためです。[2]そのため、はい、クラスはconstコンストラクターと非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 -b7ac56dbbeb1によるFlutterウィジェットの優れたパフォーマンスの勝利です。 「constを使用してウィジェットを構築するconstを使用しないと、サブツリーの選択的な再構築は行われません。Flutterはそれぞれの新しいインスタンスを作成しますサブツリー内のウィジェットはbuild()を呼び出し、特にビルドメソッドが重い場合、貴重なサイクルを無駄にしています。」
David Chandler

8

非常に詳細に説明されていますが、実際に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');
        },
      ),
    );
  }
}

この例では、テキストのタイトルのみを変更する必要があるため、このウィジェットのみを再構築する必要があるため、他のすべてのウィジェットを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]フィールド 'j'が非定数値で初期化されているため、 'const'コンストラクタを定義できません

ランタイムエラー:

/main.dart ':エラー:5行目17行目:式は有効なコンパイル時定数ではありませんfinal int j = new DateTime.now()。millisecond;

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.