Introduction

忘備録。

今更すぎる内容だけど、きちんと意味を理解したいので書いておく。

Examples

通常のコンストラクタの挙動

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
#include <iostream>

using namespace std;

class MyClass {
public:
MyClass()
{
cout << "constructor" << endl;
}
const int* get()
{
return this->m_num;
}
private:
int* m_num;
};

int main()
{
MyClass m;
const int* num1 = m.get();
cout << "*num1:" << *num1 << endl;

MyClass n = m;
const int* num2 = n.get();
cout << "*num2:" << *num2 << endl;

return 0;
}

実行すると下記のようになる。

1
2
3
constructor
*num1:-98693133
*num2:-98693133

ポイントは

  • MyClass オブジェクト m を、変数 n に代入する時にはコンストラクタは呼ばれないが、オブジェクト m の内容はコピーされる
  • オブジェクトの内容が完全にコピーされるが、メンバー変数がポインタが含まれる場合は、そのアドレスがコピーされることになる
    • 一方のオブジェクト内で m_num を操作すると、もう片方に影響を及ぼす

2 つ目が問題になる。
m_num も全く別のオブジェクトとしてコピーされるべきである。
つまり、オブジェクトの生成を行い、そのオブジェクトの中身も連鎖的にコピーされることが望ましい。

そのために存在するのがコピーコンストラクタ。

コピーコンストラクタの挙動

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
#include <iostream>

using namespace std;

class MyClass {
public:
MyClass(int num):
m_num(new int(num))
{
cout << "constructor" << endl;
}
MyClass(const MyClass& c)
{
cout << "copy constructor" << endl;
this->m_num = new int(*c.m_num);
}
const int* get()
{
return this->m_num;
}
private:
int* m_num;
};

int main()
{
MyClass m(100);
const int* num1 = m.get();
cout << "*num1:" << *num1 << "(" << num1<< ")" << endl;

MyClass n = m;
const int* num2 = n.get();
cout << "*num2:" << *num2 << "(" << num2<< ")" << endl;

return 0;
}

実行すると下記のようになる。

1
2
3
4
constructor
*num1:100(0x55b3ceb39eb0)
copy constructor
*num2:100(0x55b3ceb3a2e0)

ポイントは

  • MyClass オブジェクト m を、変数 n に代入する時にはコンストラクタは呼ばれないが、コピーコンストラクタが呼ばれるようになった
    • コピーコンストラクタ内でオブジェクトを生成するように定義したため、m_num のアドレスが別、指し示す値が同じなった

ポインタの場合

当然ながら、ポインタの代入では、コピーコンストラクタは呼ばれない。

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
#include <iostream>

using namespace std;

class MyClass {
public:
MyClass(int num):
m_num(new int(num))
{
cout << "constructor" << endl;
}
MyClass(const MyClass& c)
{
cout << "copy constructor" << endl;
this->m_num = new int(*c.m_num);
}
const int* get()
{
return this->m_num;
}
private:
int* m_num;
};

int main()
{
MyClass* m = new MyClass(100);
const int* num1 = m->get();
cout << "*num1:" << *num1 << "(" << num1<< ")" << endl;

MyClass* n = m;
const int* num2 = n->get();
cout << "*num2:" << *num2 << "(" << num2<< ")" << endl;

return 0;
}

実行すると下記のようになる。

1
2
3
constructor
*num1:100(0x5606eb6c1ed0)
*num2:100(0x5606eb6c1ed0)

ポイントは

  • MyClass オブジェクトのポインタ m を、変数 n に代入する時にはコピーコンストラクタも呼ばれない
    • ポインタ変数 m のアドレスがポインタ変数 n にコピーされただけ

挙動としては当たり前だが、new をしないクラスオブジェクトのインスタンスの作成 (スタックへの確保) は、C# や Java ではできないので、時折混乱する時がある。
new を使ってのクラスオブジェクトのインスタンスの作成 (ヒープへの確保) は、C# や Java と同じですんなり理解できる。

間接演算子 (*) による代入の場合

この場合はコピーコンストラクタが呼び出される。

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
#include <iostream>

using namespace std;

class MyClass {
public:
MyClass(int num):
m_num(new int(num))
{
cout << "constructor" << endl;
}
MyClass(const MyClass& c)
{
cout << "copy constructor" << endl;
this->m_num = new int(*c.m_num);
}
const int* get()
{
return this->m_num;
}
private:
int* m_num;
};

int main()
{
MyClass* m = new MyClass(100);
const int* num1 = m->get();
cout << "*num1:" << *num1 << "(" << num1<< ")" << endl;

MyClass n = *m;
const int* num2 = n.get();
cout << "*num2:" << *num2 << "(" << num2<< ")" << endl;

return 0;
}

実行すると下記のようになる。

1
2
3
4
constructor
*num1:100(0x55bd1fbdaed0)
copy constructor
*num2:100(0x55bd1fbdb300)

間接演算子によって、ポインタが指し示す先値を左辺に代入、つまりコピーが発生するため、コピーコンストラクタが呼ばれる。

間接演算子 (*) による参照代入の場合

この場合はコピーコンストラクタが呼び出されない。

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
#include <iostream>

using namespace std;

class MyClass {
public:
MyClass(int num):
m_num(new int(num))
{
cout << "constructor" << endl;
}
MyClass(const MyClass& c)
{
cout << "copy constructor" << endl;
this->m_num = new int(*c.m_num);
}
const int* get()
{
return this->m_num;
}
private:
int* m_num;
};

int main()
{
MyClass* m = new MyClass(100);
const int* num1 = m->get();
cout << "*num1:" << *num1 << "(" << num1<< ")" << endl;

MyClass& n = *m; // 参照
const int* num2 = n.get();
cout << "*num2:" << *num2 << "(" << num2<< ")" << endl;

return 0;
}

実行すると下記のようになる。

1
2
3
constructor
*num1:100(0x56014a576ed0)
*num2:100(0x56014a576ed0)

間接演算子によって、ポインタが指し示す先値を左辺の参照変数に代入、つまりアドレスのコピーが発生することになるため、コピーコンストラクタが呼ばれない。

アドレス演算子 (&) による代入の場合

この場合はコピーコンストラクタが呼び出されない。

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
#include <iostream>

using namespace std;

class MyClass {
public:
MyClass(int num):
m_num(new int(num))
{
cout << "constructor" << endl;
}
MyClass(const MyClass& c)
{
cout << "copy constructor" << endl;
this->m_num = new int(*c.m_num);
}
const int* get()
{
return this->m_num;
}
private:
int* m_num;
};

int main()
{
MyClass m(100);
const int* num1 = m.get();
cout << "*num1:" << *num1 << "(" << num1<< ")" << endl;

MyClass* n = &m;
const int* num2 = n->get();
cout << "*num2:" << *num2 << "(" << num2<< ")" << endl;

return 0;
}

実行すると下記のようになる。

1
2
3
constructor
*num1:100(0x55a9eabcceb0)
*num2:100(0x55a9eabcceb0)

アドレス演算子によって、クラスオブジェクト m のアドレスがポインタ変数 n に代入されただけだから、コピーコンストラクタが呼ばれなかっただけ。