aliaswith

alias

aliasキーワードは、既存の名前に対して別名を割り当てる。aliasは、alias thisとは別のものであり、関連もない。

長い名前を短縮する

前の章で見たように、名前が長すぎて使いにくい場合がある。その章で見た次の関数を考えてみよう。

Stack!(Point!double) randomPoints(size_t count) {
    auto points = new Stack!(Point!double);
    // ...
}
D

プログラム内の複数の場所でStack!(Point!double)を明示的に入力しなければならないことには、いくつかの欠点がある。

これらの欠点を解消するには、Stack!(Point!double)に新しい名前を付ける。

alias Points = Stack!(Point!double);

// ...

Points randomPoints(size_t count) {
    auto points = new Points;
    // ...
}
D

さらに進んで、一方をもう一方の利点を利用するように2つのエイリアスを定義することも考えられる:

alias PrecisePoint = Point!double;
alias Points = Stack!PrecisePoint;
D

aliasの構文は次の通りだ:

alias new_name = existing_name;
D

この定義後、新しい名前と既存の名前は同義語になる:プログラム内では同じ意味を持つ。

この機能の古い構文が一部のプログラムで遭遇する可能性がある:

    // 古い構文の使用は避けてください:
    alias existing_name new_name;

aliasは、モジュール名とともに完全に記述しなければならない名前を短縮する場合にも便利だ。Queenという名前が2つの別々のモジュール、chesspalaceに登場するとしよう。両方のモジュールがインポートされている場合、Queenとだけ入力すると、コンパイルエラーが発生する。

import chess;
import palace;

// ...

    Queen person;             // ← コンパイルエラー
D

コンパイラは、どのQueenが意図されたものかを判断できない:

エラー: chess.d(1)にあるchess.Queenが
palace.d(1)にあるpalace.Queenと競合している
Undefined

この衝突を解決する便利な方法は、1つ以上の名前に対してエイリアスを割り当てることだ:

import palace;

alias PalaceQueen = palace.Queen;

void main() {
    PalaceQueen person;
    // ...
    PalaceQueen anotherPerson;
}
D

alias他の名前でも動作する。次のコードは変数に新しい名前を割り当てる:

int variableWithALongName = 42;

alias var = variableWithALongName;
var = 43;

assert(variableWithALongName == 43);
D
設計の柔軟性

柔軟性を高めるため、intのような基本的な型にもエイリアスを付けることができる。

alias CustomerNumber = int;
alias CompanyName = string;
// ...

struct Customer {
    CustomerNumber number;
    CompanyName company;
    // ...
}
D

この構造体のユーザーが、intおよびstringの代わりに、常にCustomerNumberおよびCompanyNameと入力する場合、ユーザーコードに影響を与えることなく、将来、設計をある程度変更することができる。

これは、コードの可読性にも役立つ。変数の型をCustomerNumberと指定すると、intよりもその変数の意味に関するより多くの情報が伝わる。

このような型エイリアスは、構造体やクラス内で定義され、それらの型のインターフェースの一部になることがある。次のクラスには、weightプロパティがある。

class Box {
private:

    double weight_;

public:

    double weight() const {
        return weight_;
    }
    // ...
}
D

そのクラスのメンバー変数とプロパティはdoubleとして定義されているため、ユーザーはdoubleも使用する必要がある。

double totalWeight = 0;

foreach (box; boxes) {
    totalWeight += box.weight;
}
D

weightの型がaliasとして定義されている別の設計と比較しよう。

class Box {
private:

    Weight weight_;

public:

    alias Weight = double;

    Weight weight() const {
        return weight_;
    }
    // ...
}
D

この場合、ユーザーコードでは通常、Weightも使用される:

Box.Weight totalWeight = 0;

foreach (box; boxes) {
    totalWeight += box.weight;
}
D

この設計では、将来Weightの実際の型を変更しても、ユーザーコードには影響がない。(つまり、新しい型も+=演算子をサポートしている場合。)

スーパークラスの隠れた名前の公開

スーパークラスとサブクラスで同じ名前が使用されている場合、スーパークラスにある一致する名前は隠蔽される。サブクラスにその名前が1つでも存在すれば、スーパークラスにあるその名前と一致するすべての名前が隠蔽される:

class Super {
    void foo(int x) {
        // ...
    }
}

class Sub : Super {
    void foo() {
        // ...
    }
}

void main() {
    auto object = new Sub;
    object.foo(42);            // ← コンパイルエラー
}

引数は42であるため、int値であるため、intを受け取るSuper.foo関数がその用途のために呼び出されることを予想するかもしれない。しかし、パラメータリストは異なるものの、Sub.foo Super.fooを隠蔽し、コンパイルエラーが発生する。コンパイラはSuper.fooを完全に無視し、Sub.foointによって呼び出せないことを報告する。

エラー: 関数deneme.Sub.foo ()は、
引数の型(int)を使用して呼び出すことはできない
Undefined

これは、スーパークラスの関数をオーバーライドすることとは異なることに注意。オーバーライドの場合、関数のシグネチャは同じになり、overrideキーワードによって関数がオーバーライドされる。(overrideキーワードについては、継承の章で説明した。)

ここでは、オーバーライドではなく、名前隠蔽と呼ばれる言語機能が有効になっている。名前隠蔽がない場合、これらのクラスに追加または削除された、たまたま同じ名前fooを持つ関数は、呼び出される関数を黙って変更してしまうかもしれない。名前隠蔽は、このような予期せぬ動作を防ぐ。これは他の OOP 言語にもある機能だ。

alias 必要に応じて隠された名前を明らかにできる:

class Super {
    void foo(int x) {
        // ...
    }
}

class Sub : Super {
    void foo() {
        // ...
    }

    alias foo = Super.foo;
}
D

上記のaliasは、スーパークラスのfooの名前をサブクラスのインターフェースに持ち込む。その結果、コードはコンパイル可能になり、Super.fooが呼び出されるようになる。

必要に応じて、異なる名前で名前をサブクラスに持ち込むこともできる:

class Super {
    void foo(int x) {
        // ...
    }
}

class Sub : Super {
    void foo() {
        // ...
    }

    alias generalFoo = Super.foo;
}

// ...

void main() {
    auto object = new Sub;
    object.generalFoo(42);
}

名前隠蔽はメンバー変数にも影響する。aliasは、それらの名前もサブクラスのインターフェースに持ち込むことができる:

class Super {
    int city;
}

class Sub : Super {
    string city() const {
        return "Kayseri";
    }
}
D

一方がメンバー変数で、もう一方がメンバー関数であるかどうかに関係なく、サブクラスの名前cityは、スーパークラスの名前cityを隠す。

void main() {
    auto object = new Sub;
    object.city = 42;        // ← コンパイルエラー
}

同様に、スーパークラスのメンバー変数の名前は、aliasを使用してサブクラスのインターフェースに持ち込むことができる。必要に応じて異なる名前で:

class Super {
    int city;
}

class Sub : Super {
    string city() const {
        return "Kayseri";
    }

    alias cityCode = Super.city;
}

void main() {
    auto object = new Sub;
    object.cityCode = 42;
}
with

withは、オブジェクトまたはシンボルへの繰り返し参照を削除するためのものである。括弧で囲まれた式またはシンボルを取り、withのスコープ内で使用される他のシンボルを検索する際にその式またはシンボルを使用する。

struct S {
    int i;
    int j;
}

void main() {
    auto s = S();

    with (s) {
        i = 1;    // s.iを意味する
        j = 2;    // s.jを意味する
    }
}

括弧内に一時オブジェクトを作成することもできる。その場合、一時オブジェクトはl値となり、その有効期間はスコープを離れると終了する。

with (S()) {
    i = 1;    // 一時オブジェクトのiメンバー
    j = 2;    // 一時オブジェクトのjメンバー
}
D

後でポインタの章で見るように、newキーワードを使用して一時的なオブジェクトを構築することができ、その場合、そのオブジェクトの寿命はスコープを超えて延長される。

withは、enum型などの繰り返し参照を削除するために、caseセクションで特に役立つ。

enum Color { red, orange }

// ...

    final switch (c) with (Color) {

    case red:       // Color.redを意味する
        // ...

    case orange:    // Color.orangeを意味する
        // ...
    }
D
要約