const refパラメータとconstメンバー関数

この章では、パラメータおよびメンバー関数を、immutable変数とも一緒に使用できるように、constとしてマークする方法について説明する。constパラメータについては、前の章で説明したので、この章では、すでに知っている機能について、一部復習する。

この章の例では構造体のみを使用しているが、constメンバー関数はクラスにも適用される。

immutableオブジェクト

immutable変数を変更できないことは既に説明した。

immutable readingTime = TimeOfDay(15, 0);
D

readingTime変更できない:

readingTime = TimeOfDay(16, 0);    // ← コンパイルエラー
readingTime.minute += 10;          // ← コンパイルエラー
D

コンパイラは、immutableオブジェクトをいかなる方法でも変更することを許可しない。

ref constでないパラメーター

この概念は、関数パラメータの章で既に説明した。refとマークされたパラメータは、関数内で自由に変更することができる。そのため、関数が実際にパラメータを変更しない場合でも、コンパイラはimmutableオブジェクトをそのパラメータとして渡すことを許可しない。

/* 関数によって変更されていないにも関わらず、'duration'は
 * 'const'としてマークされていない */
int totalSeconds(ref Duration duration) {
    return 60 * duration.minute;
}
// ...
    immutable warmUpTime = Duration(3);
    totalSeconds(warmUpTime);    // ← コンパイルエラー
D

コンパイラは、immutable warmUpTimetotalSecondsに渡すことを許可しない。これは、その関数がパラメータが変更されないことを保証しないためである。

const refパラメーター

const refは、関数によってパラメータが変更されないことを意味する。

int totalSeconds(const ref Duration duration) {
    return 60 * duration.minute;
}
// ...
    immutable warmUpTime = Duration(3);
    totalSeconds(warmUpTime);    // ← 現在コンパイルできる
D

このような関数は、オブジェクトの不変性がコンパイラによって強制されるため、immutableオブジェクトをパラメータとして受け取ることができる。

int totalSeconds(const ref Duration duration) {
    duration.minute = 7;    // ← コンパイルエラー
// ...
}
D

const refの代替として、in refがある。後の章で説明するように、inは、パラメータが関数の入力としてのみ使用され、その変更は許可されないことを意味する。

int totalSeconds(in ref Duration duration) {
    // ...
}
D
const以外のメンバー関数

TimeOfDay.incrementメンバー関数で見たように、オブジェクトはメンバー関数によっても変更することができる。increment()は、呼び出されたオブジェクトのメンバーを変更する。

struct TimeOfDay {
// ...
    void increment(Duration duration) {
        minute += duration.minute;

        hour += minute / 60;
        minute %= 60;
        hour %= 24;
    }
// ...
}
// ...
    auto start = TimeOfDay(5, 30);
    start.increment(Duration(30));          // 'start'が変更される
D
constメンバー関数

一部のメンバー関数は、呼び出されたオブジェクトを変更しない。そのような関数の例として、toString()がある。

struct TimeOfDay {
// ...
    string toString() {
        return format("%02s:%02s", hour, minute);
    }
// ...
}
D

toString()の目的は、オブジェクトを文字列形式で表すことであるため、オブジェクトを変更すべきではない。

メンバー関数がオブジェクトを変更しないことは、パラメータリストの後にconstキーワードを付けることで宣言される。

struct TimeOfDay {
// ...
    string toString() const {
        return format("%02s:%02s", hour, minute);
    }
}
D

constによって、オブジェクト自体がメンバー関数によって変更されないことが保証される。その結果、toString()メンバー関数は、immutableオブジェクトでも呼び出すことができる。そうしないと、構造体のtoString()が呼び出されない。

struct TimeOfDay {
// ...
    // 不適切な設計: 'const'とマークされていない
    string toString() {
        return format("%02s:%02s", hour, minute);
    }
}
// ...
    immutable start = TimeOfDay(5, 30);
    writeln(start);    // TimeOfDay.toString()が呼び出されていない!
D

出力は期待した05:30ではなく、TimeOfDay.toStringの代わりにジェネリック関数が呼び出されたことを示している。

immutable(TimeOfDay)(5, 30)

さらに、immutableオブジェクトに対してtoString()を明示的に呼び出すと、コンパイルエラーが発生する:

auto s = start.toString(); // ← コンパイルエラー
D

したがって、前の章で定義したtoString()関数はすべて誤って設計されている。これらはconstとしてマークすべきでした。

注釈: constキーワードは、関数の定義の前に指定することもできる。

// 上記と同じ
const string toString() {
    return format("%02s:%02s", hour, minute);
}
D

このバージョンでは、constが戻り値の型の一部であるとの誤解を与える可能性があるため、パラメータリストの後に指定することをお勧めする。

inoutメンバー関数

関数パラメータの章で見たように、inoutはパラメータの変更可能性を戻り値の型に渡す。

同様に、inoutメンバー関数は、オブジェクトの変更可能性を関数の戻り値の型に渡す。

import std.stdio;

struct Container {
    int[] elements;

    inout(int)[] firstPart(size_t n) inout {
        return elements[0 .. n];
    }
}

void main() {
    {
        // 不変のコンテナ
        auto container = immutable(Container)([ 1, 2, 3 ]);
        auto slice = container.firstPart(2);
        writeln(typeof(slice).stringof);
    }
    {
        // constコンテナ
        auto container = const(Container)([ 1, 2, 3 ]);
        auto slice = container.firstPart(2);
        writeln(typeof(slice).stringof);
    }
    {
        // 変更可能なコンテナ
        auto container = Container([ 1, 2, 3 ]);
        auto slice = container.firstPart(2);
        writeln(typeof(slice).stringof);
    }
}
D
const_member_functions.1

異なる変更可能性を持つ3つのオブジェクトによって返される3つのスライスは、それらを返したオブジェクトと一致している。

immutable(int)[]
const(int)[]
int[]

constおよびimmutableオブジェクトでも呼び出さなければならないため、inoutメンバー関数は、constであるかのようにコンパイルされる。

使用方法