D言語でのstructとclassの違い

# はじめに
D言語での構造体`struct`とクラス`class`の違いについてまとめてみました。

# 初期化
`struct`には、初期化を記述するためのリテラルが用意されています。

```d:sample1.d
import std.stdio;

struct S {
	int  a;
	int  b;
}

void main(){
	S s1 = S(1, 2);
	writefln("s.a = %d, s.b = %d", s1.a, s1.b);
	S s2 = { a : 3, b : 4 };
	writefln("s.a = %d, s.b = %d", s2.a, s2.b);
}
```
```text:実行結果
s.a = 1, s.b = 2
s.a = 3, s.b = 4
```

# 値型、参照型
D言語 Language Referenceの[14.Structs, Unions](https://dlang.org/spec/struct.html)より、
>Whereas classes are reference types, structs are value types.

`struct`は値型、`class`は参照型と説明されています。
このため、代入操作を行った際の結果に違いがあります。

```d:sample2.d
import std.stdio;

struct S {
	int val;
}

class C {
	int val;
}

void main(){
	S s1 = S();
	s1.val = 3;
	S s2 = s1;
	writefln("s1.val = %s, s2.val = %s", s1.val, s2.val);
	
	s2.val = 7;
	writefln("s1.val = %s, s2.val = %s", s1.val, s2.val);
	
	C c1 = new C();
	c1.val = 3;
	C c2 = c1;
	writefln("c1.val = %s, c2.val = %s", c1.val, c2.val);
	
	c2.val = 7;	// c1.valも変更される
	writefln("c1.val = %s, c2.val = %s", c1.val, c2.val);
}
```
```text:実行結果
s1.val = 3, s2.val = 3
s1.val = 3, s2.val = 7
c1.val = 3, c2.val = 3
c1.val = 7, c2.val = 7
```
`class`の代入操作は**参照のコピー**（シャローコピー）を行っています。このため、`c1.val`も変更されることがわかります。
`class`を**複製**（ディープコピー）する場合は、以下のような複製処理の実装が必要です。

```d:sample3.d
import std.stdio;

class C {
	int val;
	this(){}
	this(C src){ val = src.val; }
	C dup(){ return new C(this); }
}

void main(){
	C c1 = new C();
	c1.val = 3;
	C c2 = c1.dup; // = new C(c1);
	writefln("c1.val = %s, c2.val = %s", c1.val, c2.val);
	
	c2.val = 7;
	writefln("c1.val = %s, c2.val = %s", c1.val, c2.val);
}
```
```text:実行結果
c1.val = 3, c2.val = 3
c1.val = 3, c2.val = 7
```

# 値渡し、参照渡し
関数呼び出しの際にも、`struct`は値渡し、`class`は参照渡しになります。
引数に`struct`を含む関数を作成する際は、値渡しをしたいのか、参照渡しをしたいのかに注意が必要です。
以下の例では、関数`f1`呼び出し時に値渡し、関数`f2`呼び出し時に参照渡しを行っています。
関数`f1`では、値渡しの際に構造体変数`s`が複製されているため、`main`関数の`s`の値は変わりません。

```d:sample4.d
import std.stdio;

struct S {
	int val;
}

void main(){
	S s = S();
	s.val = 1;
	f1(s);
	writefln("main - %d", s.val);
	
	f2(s);
	writefln("main - %d", s.val);
}

void f1(S s)
{
	s.val = 2;
	writefln("f1   - %d", s.val);
}

void f2(ref S s)
{
	s.val = 3;
	writefln("f2   - %d", s.val);
}
```
```text:実行結果
f1   - 2
main - 1
f2   - 3
main - 3
```

# 配置されるメモリ領域
`struct`の実体はスタック領域に配置されます。ただし、newを使えば、ヒープ領域にも配置可能です。
`class`の実体は、ヒープ領域に配置されます。
一般的に、スタック領域を使う方がメモリアクセスは速いといわれています。
以下の実行結果は、環境に依存しますが参考程度に。

```sample5.d
static import std.datetime.stopwatch;
import std;

struct S {
	int val;
}

class C {
	int val;
}

void f1(){S  s1 = S();}
void f2(){S* s2 = new S();}
void f3(){C  c1 = new C();}

void main(){
	auto r = std.datetime.stopwatch.benchmark!(f1, f2, f3)(1_000_000);
	writefln("%.6f", cast(double)r[0].total!"usecs" / 1_000_000);
	writefln("%.6f", cast(double)r[1].total!"usecs" / 1_000_000);
	writefln("%.6f", cast(double)r[2].total!"usecs" / 1_000_000);
}
```
```text:実行結果
D:\Dev> ldc2 -release sample5.d

D:\Dev> sample5
0.002362
0.038503
0.025301
```

# 同一性の比較
比較を行った際の結果に違いがあります。

* `struct`は、値が同じであるかを比較している。
* `class`は、参照が同じであるかを比較している。

```d:sampl6.d
import std.stdio;

struct S {
	int val;
}

class C {
	int val;
}

void main(){
	S s1 = S(1);
	S s2 = S(1);
	writefln("s1 == s2 : %s", s1 == s2);
	
	C c1 = new C();
	c1.val = 1;
	C c2 = c1;
	C c3 = new C();
	c3.val = 1;
	writefln("c1 == c2 : %s", c1 == c2);
	writefln("c1 == c3 : %s", c1 == c3);
}
```
```text:実行結果
s1 == s2 : true
c1 == c2 : true
c1 == c3 : false
```

# その他の違い
* `class`には、継承の考え方があります。それに関連して、

 * `class`は、`Object`クラスから派生している。
 * `class`は、仮想関数を作成できる。
 * `class`は、仮想関数にアクセスするための隠しプロパティが存在する。

```sample7.d
import std.stdio;

struct S {
	int val;
}

class C {
	int val;
}

void main(){
	S s = S();
	writefln("s.val = %d", s.val.offsetof);
	C c = new C();
	writefln("c.val = %d", c.val.offsetof);
}
```
64bit版でコンパイルすると、`struct`の`s.val`の配置位置は`0`ですが、`class`の`c.val`は`16`です。
`class`では、先頭から16バイトに隠しプロパティが配置されています。

```text:実行結果
D:\Dev> ldc2 sample7.d

D:\Dev> sample7
s.val = 0
c.val = 16
```

* `class`は、`synchronized`属性を付けることができます。

```d:
synchronized class C {
	private:
		int  val;
		int  getVal(){ return val; }
		void setVal(int v){ val = v; }
}
```

# 参考情報
[D言語ツアー](https://tour.dlang.org/tour/ja/welcome/welcome-to-d)に解説があります。こちらは日本語情報です。
[構造体 Struct](https://tour.dlang.org/tour/ja/basics/structs)
[クラス Class](https://tour.dlang.org/tour/ja/basics/classes)

[D言語 Language Reference](https://dlang.org/spec/spec.html)に、より詳しい情報があります。
[構造体 Struct（英語）](https://dlang.org/spec/struct.html)
[クラス Class（英語）](https://dlang.org/spec/class.html)

# おまけ：C++の場合
`C++`でも、同じテーマの記事がありました。
[C++におけるclassとstructの違い](https://qiita.com/pink_bangbi/items/e25302df7f4ba7fa0db4)
[C++のclassとstructの違い](http://adragoona.hatenablog.com/entry/2015/09/20/205704)

* `C++`でも`struct`で関数定義できるとは知りませんでした。  `C++`にはなくて、`D言語`にある特徴だと思っていました。
* `C++`の`class`は、`new`しなくても利用可能です。
* `C++`と違って、`D言語`では`struct`も`class`もデフォルトの可視属性は`public`です。