🌵

コピーコンストラクタはなぜ必要か?【C++】

2022/04/08に公開

コピーコンストラクタが必要になるのは「クラスが外部の資源を管理するとき」です。具体的には「クラスがポインタを持っていた場合」などです。

NewClass obj1;
NewClass obj2 = obj1;

というコードを具体例として考えてみます。

1、値をコピーする場合
2、クラスが外部の資源を管理する場合

と2つに分けてご説明していきます。

1、値をコピーする場合

クラスが単純に変数numしか持っていなかった場合です。

class NewClass{
    public:
        int num;
};

この場合「アドレスの共有・勝手な書き換え」が問題になることはありません。なぜならコピーされるのはnumの値だけだからです。図にすると以下のような形になります。

2、クラスが外部の資源を管理する場合

「アドレスの共有・勝手な書き換え」が問題になるのは、クラスの管理する資源が外部ある場合です。わかりやすいのがクラス内にポインタがある場合です。

class NewClass{
    public:
        int *p;
};

ポインタが持つのはアドレスであり、データの本体はクラスとは全く別の場所にあります。

図にすると上記のような形です。ポインタなどを持つクラスをコピーすると、アドレスがコピーされることになります。アドレスがコピーされると1つの資源を2つで参照することになり、値の勝手な書き換えが問題となります。 このような場合コピーコンストラクタの仕組みが必要となります。

コピーコンストラクタは初期化だけ

オブジェクトをコピーした際に「外部資源の共有」が問題になることを見てきました。今回はNewClass obj2 = obj1;という初期化の例で考えましたが、代入の場合でも同じです。要するに、

NewClass obj1;
NewClass obj2 = obj1;

でも、

NewClass obj1;
NewClass obj2;
obj1 = obj2

でも、外部資源の共有が問題になります。なぜならクラスがポインタを持っていれば、どちらの場合もアドレスだけがコピーされてしまうからです。 そしてここで注意して頂きたいのが、コピーコンストラクタが使えるのは「初期化」の時だけで「代入」には使えない点です。

結論からいえば、

初期化:コピーコンストラクタ
代入:演算子のオーバーロード

と、それぞれ別々の仕組みで「外部資源の共有」という問題に対処する必要があります。

初期化と代入の違い

初期化と代入は全く異なる仕組みです。

初期化:変数の宣言と同時に、値を代入
代入:変数の宣言後に、値を代入

という違いがあります。具体的なコードは以下です。

■初期化:変数の宣言と同時に、値を代入

num1 = 10;
num2 = num1;

■代入:変数の宣言後に、値を代入

num1 = 10;
num2;
num2 = num1;

どちらも結果的にはnum2に対して10という数値を代入しています。しかしプログラムからは初期化・代入として明確に区別されます。初期化は変数を宣言した際に一度しかできません。代入は後から何回でもできます。

コピーコンストラクタは「初期化」の際に、外部資源の共有問題へ対処するために必要な仕組みです。しかし「代入」にコピーコンストラクタを使うことはできません。代入の際にも資源の勝手な書き換えを防ぎたい場合、演算子のオーバーロードの仕組みが必要となります。

クラスが保有している情報によっては勝手な値の書き換えが問題となりますので、注意をして下さい!

Discussion