英語版
このページの英語版を見る

型修飾子

型修飾子はTypeCtorを適用して型を変更する。 TypeCtorは以下のとおりである:const immutablesharedinout である。 それぞれすべてのサブタイプに適用される。

ConstとImmutable

データ構造やインタフェースを調べるとき、どのデータが不変でないことが予想されるかを簡単に見分けることができると非常に便利である。 データ構造やインタフェースを調べるとき、どのデータが変更されないと予想されるのか、どのデータが変更される可能性があるのか どのデータが変更されないと予想されるか、どのデータが変更される可能性があるのか、そして誰がそのデータを変更する可能性があるのかを簡単に知ることができるのは、非常に便利である。 これは、言語型システムの助けを借りて行う。 データはconstまたはimmutableとマークすることができ、デフォルトはchangeable(またはmutable)である。 changeable(またはmutable)である。

immutable は変更できないデータに適用される。 不変のデータ "は、一度構築されると、プログラムの実行中ずっと同じ値のままである。 プログラムの実行中 である。 不変のデータは、ROM(読み出し専用メモリ)または、ハードウェアによって読み出し専用とマークされたメモリ・ページに置くことができる。 メモリ・ページに置くことができる。 不変のデータ "は変化しないため、プログラムを最適化する多くの機会が得られる。 プログラムの最適化を可能にし、関数型プログラミングに応用できる。 スタイルのプログラミングに応用できる。

const イミュータブル・データは によって変更できないデータに適用される。しかし によって変更することはできる。 constは、データを変更しないと約束されているインターフェイスを経由してデータを受け渡す際に適用される。 constは、データを変更しないことを約束するインターフェイスを通してデータを渡す際に応用される。

immutableもconstも推移的である。 つまり、不変の参照を通して到達可能なデータもまた不変である。 constも同様である。

イミュータブル・ストレージ・クラス

最も単純なイミュータブル宣言では、ストレージ・クラスとして使用する。 これは、マニフェスト定数を宣言するために使用できる。

immutable int x = 3;  // xは3に設定される
x = 4;        // エラー、xは不変である
char[x] s;    // sは3文字の配列である

型はイニシャライザから推測できる:

immutable y = 4; // yはint型である
y = 5;           // エラー、yは不変である

イニシャライザがない場合、イミュータブルは対応するコンストラクタから初期化できる。 は対応するコンストラクタから初期化される:

immutable int z;
void test()
{
    z = 3; // エラー、zは不変である
}
static this()
{
    z = 3; // OK、静的イニシャライザーを持たない
           // immutableを設定できる
}

非ローカル不変宣言のイニシャライザは、評価可能でなければならない。 評価可能でなければならない。 でなければならない:

int foo(int f) { return f * 3; }
int i = 5;
immutable x = 3 * 4;      // OK、12
immutable y = i + 1;      // エラー、コンパイル時に評価できない
immutable z = foo(2) + 1; // OK、foo(2)はコンパイル時に評価できる

非静的ローカル不変宣言のイニシャライザは、実行時に評価される。 のイニシャライザは実行時に評価される:

int foo(int f)
{
    immutable x = f + 1;  // 実行時に評価される
    x = 3;                // エラー、xは不変である
}

不変は推移的なので、不変によって参照されるデータも不変である。 も不変である:

immutable char[] s = "foo";
s[0] = 'a';  // エラー、sは不変のデータを参照する
s = "bar";   // エラー、sは不変である

イミュータブル宣言はl値として現れることができる。 イミュータブル宣言はl値として現れることができる。

constストレージ・クラス

const宣言は、不変宣言とまったく同じである、 以下の違いがある:

不変型

値が決して変化しないデータは、immutable型として型付けすることができる。 immutableキーワードは型修飾子として使用できる:

immutable(char)[] s = "hello";

immutableは、次の括弧内の型に適用される。 つまり、s に新しい値を代入することができる、 s[] :

s[0] = 'b';  // エラー、s[]は不変である
s = null;    // OK、s自身は不変ではない

イミュータブルは他律的である。 つまり、不変型から参照できるものすべてに適用される:

immutable(char*)** p = ...;
p = ...;        // OK、pは不変ではない
*p = ...;       // OK、*pは不変ではない
**p = ...;      // エラー、**pは不変である
***p = ...;     // エラー、***pは不変である

ストレージ・クラスとして使われるイミュータブルは、イミュータブル型の型修飾子としてイミュータブルを使うのと等価である。 宣言の型全体に対する型修飾子としてimmutableを使うのと等価である。 宣言の型全体の型修飾子としてimmutableを使うのと同じことである:

immutable int x = 3;   // xはimmutable(int)として型付けされている
immutable(int) y = 3;  // yは不変である

不変のデータを作成する

最初の方法は、すでにイミュータブルなリテラルを使うことだ、 文字列リテラルなどである。文字列リテラルは常に不変である。

auto s = "hello";   // sはimmutable(char)[5]である
char[] p = "world"; // エラー、暗黙的にimmutableを
                    // mutableに変換できない

二つ目の方法は、データを不変のデータにキャストすることである。 その際、同じデータへの変更可能な参照が使われないようにするのはプログラマー次第である。 の参照がキャスト後のデータの変更に使われないようにすることだ。 キャストの後にデータを変更するために同じデータへのミュータブル参照が使われないようにすることだ。

char[] s = ['a'];
s[0] = 'b'; // OK
immutable(char)[] p = cast(immutable)s; // OK、もしデータが
                                        // もうsを通して変更されないなら
s[0] = 'c'; // 未定義の動作
immutable(char)[] q = cast(immutable)s.dup; // 常に一意参照でOK

char[][] s2 = [['a', 'b'], ['c', 'd']];
immutable(char[][]) p2 = cast(immutable)s2.dup; // 危険、
                                                // 要素の最初のレベルだけが一意である
s2[0] = ['x', 'y']; // OK、p2には影響しない
s2[1][0] = 'z'; // 未定義の動作
immutable(char[][]) q2 = [s2[0].dup, s2[1].dup]; // 常にOK、一意参照

.idup "プロパティは、配列のイミュータブルコピーを作成する便利な方法である。 プロパティを使うと便利だ:

auto p = s.idup;
p[0] = ...;       // エラー、p[]は不変である

キャストによる不変または定数の削除

immutableまたはconst型修飾子はキャストで削除できる:

immutable int* p = ...;
int* q = cast(int*)p;

しかし、これはデータを変更できるという意味ではない:

*q = 3; // コンパイラによって許可されるが、結果は未定義の振る舞いである

イミュータブル・コレクトネスをキャストで取り除く機能は、静的型付けが正しくない場合に必要である。 イミュータブル・コレクトネスをキャストで取り除く機能は、静的型付けが不正確で修正できないような場合に必要となる。 たとえば、変更できないライブラリのコードを参照する場合などだ。 キャスティングはいつものように、鈍感で効果的な道具である。 イミュータブル・コレクトネスを取り除くためにキャスティングを使用する場合、次のような責任を負わなければならない。 データの不変性を保証する責任を負わなければならない。 というのも、コンパイラーはもはや静的にそうすることができないからだ。

未定義の振る舞い: const 修飾子をキャスト・アウェイして、それを変異させる、 参照されるデータが変更可能な場合でもである。これは これは、コンパイラーやプログラマーがconst だけから推測できるようにするためである。例えば 例えば、fx を変更しないと仮定することができる:
void f(const int* a);
void main()
{
    int x = 1;
    f(&x);
    assert(x == 1); // 保持が保証される
}

不変のメンバ関数

不変のメンバ関数は、そのオブジェクトと、 の参照によって参照されるすべてのものが、不変であることを保証する。 およびthis 参照によって参照されるものはすべて が不変であることを保証する。 これらは次のように宣言される:

struct S
{
    int x;

    void foo() immutable
    {
        x = 4;      // エラー、xは不変である
        this.x = 4; // エラー、xは不変である
    }
}

メソッドの左辺でimmutableを使用しても、戻り値の型には適用されないことに注意:

struct S
{
    immutable int[] bar()  // barは不変、返却型は不変でない!
    {
    }
}

戻り値の型をイミュータブルにするには、括弧で囲む:

struct S
{
    immutable(int[]) bar()  // barはmutableになり、返却型はimmutableになった
    {
    }
}

戻り値の型とメソッドの両方をイミュータブルにするには、こう書く:

struct S
{
    immutable(int[]) bar() immutable
    {
    }
}

と書く。

const型は不変型と似ているが、const型はデータの読み取り専用ビューを形成する。 はデータの読み取り専用ビューを形成する。そのデータの他のエイリアス 同じデータに対する他のエイリアスは、いつでもそのデータを変更することができる。

constメンバ関数

constメンバ関数は、メンバ関数を通じてオブジェクトのいかなる部分も変更することができない関数である。 メンバ関数は、メンバ関数の参照を通じてオブジェクトのいかなる部分も変更することができない関数である。 this 参照を通じてオブジェクトのいかなる部分も変更することができない関数である。

インアウト

パラメータが変更可能か、const か、immutable かだけが異なる関数は、組み合わせることができる、 immutable を持つ関数は、const 型コンストラクタを使って1つの関数にまとめることができる。 inout "型コンストラクタを使って1つの関数にまとめることができる。以下を考えてみよう。 オーバーロード・セット

int[] slice(int[] a, int x, int y) { return a[x .. y]; }

const(int)[] slice(const(int)[] a, int x, int y) { return a[x .. y]; }

immutable(int)[] slice(immutable(int)[] a, int x, int y) { return a[x .. y]; }

これらの各関数によって生成されるコードは同一である。 inout型コンストラクタは、これらを1つの関数にまとめることができる:

inout(int)[] slice(inout(int)[] a, int x, int y) { return a[x .. y]; }

inoutキーワードは、以下を表すワイルドカードとなる。 mutable、constimmutableinout 、またはinout const 。 この関数を呼び出すと、戻り値のinout の状態が変更される。 inout に変更される。

inout を持つ関数の内部で、型コンストラクタとして使用することもできる。 パラメータを持つ関数の内部で、型コンストラクタとして使用することもできる。で宣言された型の 状態は、引数の型と一致するように変更される。 で宣言された型の状態は、パラメータに渡された引数の型と一致するように変更される。 パラメータに渡される引数の型に合わせて変更される: inout inout inout inout

inout(int)[] asymmetric(inout(int)[] input_data)
{
    inout(int)[] r = input_data;
    while (r.length > 1 && r[0] == r[$-1])
        r = r[1..$-1];
    return r;
}

Inout型は、暗黙のうちにconst またはinout const 、 に暗黙的に変換することができる。その他の型はinout に暗黙的に変換することはできない。 @safe 関数では、inout へのキャストまたは からのキャストはできない。

void f(inout int* ptr)
{
    const int* p = ptr;
    int* q = ptr; // エラー
    immutable int* r = ptr; // エラー
}

inout パラメータにマッチする

inout パラメータを持つ関数の引数の集合は、次のようにみなされる。 inout の引数の型が完全に一致する場合、または:

  1. inout 型からなる引数型がない。
  2. immutable 引数型がconst 型で構成されていない。 対応するパラメータinout 型に対してマッチさせることができる。

このようなマッチが発生した場合、inout は、マッチした修飾子の共通修飾子とみなされる。 の共通修飾子とみなされる。2つ以上のパラメータが存在する場合、共通修飾子の計算は再帰的に適用される。 の計算が再帰的に適用される。

つの型修飾子の共通修飾子
mutableである。constimmutableinoutinout const
mutable(= m)mcccc
const (= c)ccccc
immutable (= i)cciwcwc
inout (= w)ccwcwwc
inout const (= wc)ccwcwcwc

リターン型のinout は、次のように書き換えられる。inout 修飾子に合わせて書き直される:

int[] ma;
const(int)[] ca;
immutable(int)[] ia;

inout(int)[] foo(inout(int)[] a) { return a; }
void test1()
{
    // inoutはmutableにマッチするので、inout(int)[]は
    // int[]に書き換えられる
    int[] x = foo(ma);

    // inoutはconstにマッチするので、inout(int)[]は
    // const(int)[]に書き換えられる
    const(int)[] y = foo(ca);

    // inoutはimmutableにマッチするので、inout(int)[]は
    // immutable(int)[]に書き換えられる
    immutable(int)[] z = foo(ia);
}

inout(const(int))[] bar(inout(int)[] a) { return a; }
void test2()
{
    // inoutはmututableにマッチするので、inout(const(int))[]は
    // const(int)[]に書き換えられる
    const(int)[] x = bar(ma);

    // inoutはconstにマッチするので、inout(const(int))[]は
    // const(int)[]に書き換えられる
    const(int)[] y = bar(ca);

    // inoutはimmutableにマッチするので、inout(int)[]は
    // immutable(int)[]に書き換えられる
    immutable(int)[] z = bar(ia);
}

注釈:共有型は と一致させることはできない。 inout とはマッチしない。

共有型

複数のスレッド間で共有されるような変更可能なデータは、 修飾子を付けて宣言する必要がある。 shared 。これにより、データに対する非同期 データ・レースが発生する。 shared 型属性は(constimmutable のように)推移的である。

shared int x;
shared(int)* p = &x;
//int* q = p; // エラー、qは共有されない

基本的なデータ型では、読み書きは通常アトミック操作で行える。 アトミック操作で行える。ポータビリティのために core.atomicを使う:

import core.atomic;

shared int x;

void fun()
{
    //x++; // エラー、代わりにatomicOpを使う
    x.atomicOp!"+="(1);
}

警告:警告:共有データに対する個々の読み取りまたは書き込み操作は は、デフォルトではまだエラーではない。これらを検出するには -preview=nosharedaccess コンパイラー・オプションを使用する。通常の初期化は はエラーなしで許可される。

import core.atomic;

int y;
shared int x = y; // OK

//x = 5; // プレビューフラグ付き書き込みエラー
x.atomicStore(5); // OK
//y = x; // エラーをプレビューフラグ付きで読み込む
y = x.atomicLoad(); // OK
assert(y == 5);

キャスト

より大きな型を扱う場合、手動同期化 を使うことができる。そのためには、shared 。 相互排他が確立されている間、キャストすることができる:

struct T;
shared T* x;

void fun()
{
    synchronized
    {
        T* p = cast(T*)x;
        // `*p`を操作する
    }
}

非共有参照を共有にキャストできるのは、キャスト結果の有効期間中にソース・データにアクセスしない場合だけである。 非共有参照を共有にキャストできるのは、キャスト結果の有効期間中、ソース・データにアクセスしない場合に限られる。

class C {}

@trusted shared(C) create()
{
    auto c = new C;
    // エスケープせずにcを操作する
    return cast(shared)c; // OK
}

共有グローバル変数

グローバル(または静的)共有変数は、スレッド間でアクセス可能な共通のストレージに格納される。 に格納される。グローバル変数は変更可能である。 スレッドローカルストレージに格納される。

グローバル/静的データを、コンパイラのチェックを受けずに複数のスレッドで暗黙的に共有することを宣言するには、以下を参照のこと。 コンパイラのチェックを受けずに複数のスレッドで暗黙的に共有することを宣言するには __gshared.

修飾子の組み合わせ

1つの型に複数の修飾子を適用することができる。適用順序は関係ない。 例えば、Tconst shared T 、 は同じ型である。 shared const T は同じ型である。そのため、本文書では 修飾子の組み合わせは、必要でない限り括弧を付けず、アルファベット順に記述する。 の順に記述する。

すでにその修飾子を持っている型に修飾子を適用することは合法であるが、何の効果もない。 例えば、未修飾の型Tshared(const shared T)const shared T という型を返す。

immutable 修飾子を任意の型(修飾されているかどうかにかかわらず)に適用すると、次のようになる。 immutable T.任意の修飾子をimmutable T に適用すると、次のようになる。 immutable T.これにより、immutable は修飾子の組み合わせの固定点となり、 のような型を作ることが不可能になる。 const(immutable(shared T)) のような型を作ることが不可能になる。
alias SInt = shared int;
alias IInt = immutable int;
static assert(is(immutable(SInt) == IInt));
static assert(is(shared(IInt) == IInt));

T を非修飾型と仮定すると、以下のグラフは修飾子がどのように組み合わされるかを示している。 修飾子がどのように組み合わされるかを示している(immutable との組み合わせは省略されている)。各ノードについて、 エッジにラベル付けされた修飾子を適用すると、結果として型が得られる。

Qualifier combination rules

暗黙の修飾子変換

変更可能な間接関数を持たない値(変更可能な間接関数を持たない構造体を含む。 は変更可能")にまたがって暗黙的に変換できる。 mutable,const,immutable,const shared,inout および inout shared.

修飾されたオブジェクトへの参照は、以下のルールに従って暗黙的に変換できる。 を暗黙的に変換できる:

Qualifier conversion rules

上のグラフでは、有向パスはすべて合法的な暗黙変換である。上記のグラフでは、どのような有向パスも合法的な暗黙変換である。 示されたもの以外の修飾子の組み合わせは有効ではない。もし有向パス が存在する場合、このようにして修飾された型は「修飾子変換可能型」と呼ばれる。 と呼ばれる同じ情報を以下に表形式で示す。 形式で示す:

参照型の暗黙的変換
から/へミュータブルconstsharedinoutconst sharedconst inoutinout sharedconst inout sharedimmutable
mutable
const
shared
inout
const shared
const inout
inout shared
const inout shared
immutable

一意な式

テーブルによって暗黙的な変換が許可されていない場合、式は次のように暗黙的に変換される。 は以下のように暗黙的に変換される:

一意な式とは、その式の値への参照が他になく、その式が遷移的に参照するすべての式が一意であるものである。 また、その式が推移的に参照するすべての式も一意であるか、不変である。 も一意であるか、不変である。例えば、以下のようになる:

void main()
{
    immutable int** p = new int*(null); // OK、ユニーク

    int x;
    //immutable int** q = new int*(&x); // エラー、xへの参照が他にもあるかもしれない

    immutable int y;
    immutable int** r = new immutable(int)*(&y); // OK、yは不変である
}

以下も参照のこと:純粋ファクトリ関数」も参照のこと。

それ以外の場合、CastExpressionは、暗黙的なバージョンが許可されていない場合に、強制的に変換するために使用することができる。 を使用することができるが、これは@safe コードでは実行できない、 そして、その正しさはユーザーによって検証されなければならない。

"...