クラス

C++ ではクラスと構造体はデフォルトのアクセス保護属性がことなるだけだった.

一方, D 言語のクラスは参照型, 構造体は値型であるから, まったく別物だ.

inheritance.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import std.stdio;

class Foo{
  int a;
  this(){
    writeln("create");
    a = 1;
  }
/*
  デフォルトコンストラクタ this(){}
*/
  this(int i){
    this();
/*
  コンストラクタの中で別のコンストラクタを呼び出すことができる.
  初期化処理を共通化できる.
*/
    a = i;
  }
/*
  引数付きのコンストラクタ this(int i){}
*/
  ~this(){
    writeln("destroy");
  }
/*
  デストラクタは ~this という関数.
  ガベージコレクタ (GC) がオブジェクトを削除するときに呼び出される.
  main 関数のスコープを抜けてプログラムが終了する段階で呼び出されて
  いることに注意!!
*/
  static this(){
    writeln("static this (実行直後に呼ばれる)");
  }
/*
  static this はアプリケーション実行直後に呼ばれる.
*/
  static ~this(){
    writeln("static ~this (終了直前に呼ばれる)");
  }
/*
  static ~this はアプリケーション終了直前に呼ばれる.
*/
}// C++ みたいなセミコロンが必要ない!!

void main(){
  Foo x = new Foo();
  writeln(x.a);

  Foo y = new Foo(2);
  writeln(y.a);
/*
  クラスのメンバへのアクセスにはドット (.) を用いる.
  コロン二つ (::) やアロー演算子 (->) はない.
*/
}

inheritance.d の実行結果は:

[cactus:~/code_d/d_tuts]% ./inheritance
static this (実行直後に呼ばれる)
create
1
create
2
static ~this (終了直前に呼ばれる)
destroy
destroy

D 言語には GC があるから, 明示的に削除しなくてもオブジェクトは自動的に回収されて削除される.

明示的に削除したい場合は delete を使うことができる:

A a = new A;
delete a;

なお, C++ では クラスのデフォルトアクセス保護属性は private だった.

D 言語のクラスのデフォルトアクセス保護属性は public である.

また, 同じソースファイルに書かれたすべてのコードはお互いに friend 関係とみなされ, public 扱いになる.

クラス / 構造体のデフォルトアクセス保護属性は public

同じソースファイル内の全てのコードはお互いに public

継承

inheritance2.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import std.stdio;

class A{
}
class B:A{
}
/*
  セミコロン (;) の後ろに親クラスを書く C++ 形式.
  多重継承は禁止, インタフェースを使う.
  デフォルトは public 継承, private 継承, protected 継承もできる.
*/

void main(){
  A x = new A;
  B y = new B;
  A z = new B;
}

関数オーバーライド

override.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import std.stdio;

class A{
  void draw(){
    writeln("A.draw");
  }
  void clear(){
    writeln("A.clear");
  }
/*
  メンバ関数はデフォルトで仮想関数なので virtual は不要.
  オーバーライドしていないメンバ関数は自動的に非仮想呼び出しされるので,
  パフォーマンスも安心.
*/
}

class B : A{
  override void draw(){
    writeln("B.draw");
  }
  override void clear(){
    writeln("B.clear");
  }
/*
  なるべく override は明示的に書くべき, override キーワードで
  メンバ関数を修飾すると, オーバーライドされる関数がないときに
  エラーになる.
*/
}

void main(){
  A x = new A;
  x.draw();
  x.clear();

  B y = new B;
  y.draw();
  y.clear();

  A z = new B;
  z.draw();
  z.clear();
}

override.d の実行結果は:

[cactus:~/code_d/d_tuts]% ./override
A.draw
A.clear
B.draw
B.clear
B.draw
B.clear

インタッフェース

C++ の抽象基底クラスは不恰好だった:

class IDrawable{
  virtual void draw() = 0;
  virtual void clear() = 0;
}

class Canvas : public IDrawable{
public:
  virtual void draw(){}
  virtual void canvas(){}
}; // セミコロンが必要となる.

D 言語では, いまどきの [インタフェース] を定義できる:

interface IDrawable{
  void draw();
  void clear();
}

class Canvas : IDrawable{
  void draw(){}
  override void clear(){}
} // セミコロンが必要ない.

インタフェースの関数は実装を持っていない:

interface IDrawable{
  void draw(){} // error
  ...
}

抽象クラス

インタフェースに実装を含めることはできないが, 抽象クラスならそれが可能だ.

ただし, 抽象クラスをインスタンス化 (new) することはできないので, 必ず抽象 クラスを継承しサブクラスをインスタンス化する:

abstract class Abstract{}

class Concrete : Abstract{}

void main(){
  Abstract y = new Concrete();
  // Abstract x = new Abstract(); // error
}

演算子オーバーロード

C++ ではクラス / 構造体のメンバー関数あるいは非メンバ関数で演算子オーバーロード した.

D ではすべての演算子オーバーロードをメンバー関数で行う. たくさんの演算子がオーバーロードできる.

まず, D では opUnary メンバー関数と opBinary メンバー関数を定義することで演算子オーバーロードする.

下記のように, メンバ関数テンプレートを使った記法になっている.

overload.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import std.stdio;

class A{
  this(int v){
    this.v = v;
  }
  int v;
  A opUnary(string op)() if (op == "-"){
    return new A(-this.v); // 単項マイナス演算子
  }
  A opBinary(string op)(A other) if (op == "+"){
    return new A(this.v + other.v); // 二項プラス演算子
  }
}

void main(){
  A a = new A(1);
  A b = new A(2);
  A c = a + b; // 二項プラス演算子
  writeln("二項プラス演算子: ", c.v);

  A d = -c; // 単項マイナス演算子
  writeln("単項マイナス演算子: ", d.v);
}

overload.d の実行結果は:

[cactus:~/code_d/d_tuts]% ./overload
二項プラス演算子: 3
単項マイナス演算子: -3

続いて, C++ では関数オブジェクトを作るために用いる operator() 演算子は, D では opCall である:

struct Functor{
  int opCall(int x, int y, int z);
}
void test(){
  Functor f;
  int i;
  i = f(1, 2); // i = f.opCall(1, 2); と同じ
}

プロパティ

C++ にはプロパティ専用の構文はなかった.

一方, D のプロパティは関数の呼び出し方法にシンタックスシュガーを用意することで 実現されている.

property.d

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import std.stdio;

class Foo{
  private int foo_; // read/write
  private int bar_; // read only

  public int foo(){ // read
    return foo_;
  }

  public void foo(int i){ // write
    foo_ = i;
  }

  public int bar(){ // read
    return bar_;
  }
/*
  今後必須になる可能性があるので, () を省略したい関数には @property 属性
  をつけておくことを推奨する.
  // 例
  int bar() @property{
    return 100;
  }

*/

  this(){
    foo_ = 10;
    bar_ = 20;
  }
}

void main(){
  Foo x = new Foo;

  // 次の二つは同じ
  x.foo(1);
  x.foo = 1;
/*
  引数を一つしか持たない関数は
  func(100); のシンタックスシュガーとして func = 100; を使える.
*/

  // 次の二つは同じ
  writeln( x.foo ); // 引数を持たない関数名の後ろの () は省略できる.
  writeln( x.foo() );

  // 次の二つはエラー
  // x.bar(2);
  // x.bar = 2;

  // 次の二つは同じ
  writeln( x.bar );
  writeln( x.bar() );
}

property.d の実行結果は:

[cactus:~/code_d/d_tuts]% ./property
1
1
20
20

const / immutable メンバ変数

C++ に const メンバ変数があるように D には const/immutable メンバ変数ある.

D には初期化子リストがないので代わりにコンストラクタの中で代入して初期化する:

class A{
  const int i;
  this(){
    i = 0;
    i = 1;
  /*
    const/immutable メンバ関数はコンストラクタの中でしか書き換えられない.
  */
  }
  void f(){
    // i = 0; // エラー
  }
}

const / immutable メンバ関数

C++ に const メンバ関数があるように D には const/immutable メンバ関数がある.

immutale メンバ関数がある分, 複雑になっている:

class A{
  int x;
  void f(){
    x = 0;
    f();
    g();
    // h(); // immutable メンバ関数は immutable メンバ関数からしか呼び出せない.
  }
  void g() const{
    // x = 0; // const メンバ関数ではメンバ変数を書き換えられない
    // f(); // const でないメンバ関数は状態を書き換える可能性があるので呼び出せない.
    g();
    // h();
  }
  void h() immutable{
    // x = 0; // immutable メンバ関数ではメンバ変数を書き換えられない
    // f(); // 状態を書き換え可能性があるので呼び出せない
    g(); // 状態を書き換えないので呼び出せる
    h(); // 状態を書き換えないので呼び出せる
  }
}