web-dev-qa-db-ja.com

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

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

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

73
markovuksanovic

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

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

追伸.

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

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

54
mezoni

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

Dart定数コンストラクター

私がコンテンツをコピーしても構わないことを願っています。

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

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

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

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

これにより、クラスとコンストラクターにいくつかの制限が課されます。 constコンストラクターは本体を持つことができず(ステートメントは実行されません!)、そのクラスには非最終フィールドを含めることはできません(コンパイル時に「知っている」値は後で変更できません)。初期化子リストは、フィールドを他のコンパイル時定数にのみ初期化する必要があるため、右側は「コンパイル時定数式」[1]に制限されます。そして、「const」という接頭辞を付ける必要があります-そうでなければ、それらの要件を満たすたまたま通常のコンストラクターを取得するだけです。それはまったく問題ありません。constコンストラクターではありません。

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

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

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

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

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

/ L

[1]または実際:「潜在的にコンパイル時の定数式」は、コンストラクターパラメーターも参照する場合があるためです。 [2]そのため、クラスにはconstコンストラクタと非constコンストラクタの両方を同時に含めることができます。

このトピックは https://github.com/Dart-lang/sdk/issues/36079 で興味深いコメントとともに議論されました。

47

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がチェックします。

ダーツ分析:

[Dart]フィールド 'j'が非定数値で初期化されているため、 'const'コンストラクターを定義できません

ランタイムエラー:

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

0
Ticore Shih