C++で独自の代入演算子を定義すべきケースとは?
クラスにポインタメンバが含まれていない場合、独自の代入演算子やコピーコンストラクタを定義する必要はありません。C++コンパイラは、各クラスに対してコピーコンストラクタと代入演算子を自動的に生成します。しかし、デフォルトで生成される演算子では期待通りに動作しないケースでは、独自の代入演算子を定義しなければなりません。
デフォルトの代入演算子が引き起こす問題
次の例では、ポインタメンバを持つクラスに対して、デフォルトの代入演算子がどのように動作するかを確認できます。
#include<iostream>
using namespace std;
class MyClass { // ユーザー定義の代入演算子・コピーコンストラクタは存在しない
int *ptr;
public:
MyClass (int x = 0) {
ptr = new int(x);
}
void setValue (int x) {
*ptr = x;
}
void print() {
cout << *ptr << endl;
}
};
main() {
MyClass ob1(50);
MyClass ob2;
ob2 = ob1;
ob1.setValue(100);
ob2.print();
}出力結果
100
main()関数では、setValue()メソッドを使ってob1の値を100に変更しています。ところが、その変更がob2にも反映されてしまいました。このような予期しない動作は、プログラムに深刻なバグをもたらす可能性があります。
原因は、ユーザー定義の代入演算子が存在しないため、コンパイラが自動生成したものが使われている点にあります。自動生成された代入演算子は、右辺(RHS)のptrの値を左辺(LHS)にそのままコピーするだけです。その結果、両オブジェクトのポインタが同一のメモリ領域を指す「浅いコピー(シャローコピー)」となり、片方の変更がもう片方に影響してしまいます。
問題の解決方法
この問題を解決するには、主に2つの方法があります。
1. コピーを禁止する:ダミーのプライベート代入演算子を宣言し、オブジェクトのコピー自体を禁止する
2. 独自の代入演算子を定義する:ディープコピー(深いコピー)を行う代入演算子を実装する
ここでは、ディープコピーを行う独自の代入演算子を実装した例を紹介します。
#include<iostream>
using namespace std;
class MyClass {
int *ptr;
public:
MyClass (int x = 0) {
ptr = new int(x);
}
void setValue (int x) {
*ptr = x;
}
void print() {
cout << *ptr << endl;
}
MyClass &operator=(const MyClass &ob2) {
// 自己代入のチェック
if(this != &ob2)
*ptr = *(ob2.ptr);
return *this;
}
};
main() {
MyClass ob1(50);
MyClass ob2;
ob2 = ob1;
ob1.setValue(100);
ob2.print();
}出力結果
50
今回は、独自の代入演算子によってディープコピーが行われ、各オブジェクトが独立したメモリ領域を持つようになりました。ob1の値を変更しても、ob2には影響しません。
注意点:自己代入のチェック
独自の代入演算子を実装する際は、自己代入(ob1 = ob1 など)のチェックを必ず行いましょう。チェックを怠ると、自分自身の値を自分に代入する過程で、現在のオブジェクトの値が失われてしまう恐れがあります。
補足:3のルール(Rule of Three)
C++では、コピーコンストラクタ、代入演算子、デストラクタのいずれかを独自に定義する必要があるクラスは、通常3つすべてを定義すべきとされています。これは「3のルール(Rule of Three)」として知られる設計指針です。動的メモリを管理するクラスでは、このルールに従うことで、リソースリークや二重解放といった問題を防ぐことができます。
-
C++の単項演算子とは?種類・使い方を実例付きで解説
単項演算子(unary operator)とは、1つのオペランド(被演算子)に対して作用し、新しい値を生成する演算子のことです。C++には以下のような単項演算子が用意されています。 C++の主な単項演算子一覧 演算子説明 間接参照演算子(*)ポインタ変数に作用し、そのポインタが指すアドレスにある値に相当するlvalueを返します。この操作はポインタの「デリファレンス(間接参照)」と呼ばれます。 アドレス取得演算子(&)オペランドのメモリアドレスを取得する単項演算子です。オペランドとしては、関数指定子、またはビットフィールドでなく、register記憶クラス指定子を付けずに宣言さ
-
C#の代入演算子とは?種類と使い方をサンプルコード付きで解説
C#の代入演算子とは C#の代入演算子は、変数に値を格納するために使用される演算子です。基本となる「=」のほかに、算術演算やビット演算と代入を1つの式でまとめて行える複合代入演算子が多数用意されています。複合代入演算子を活用すると、コードが簡潔になり、可読性の向上にもつながります。 以下の表は、C#で利用できる主な代入演算子の一覧です。 演算子説明使用例 =単純代入演算子。右側のオペランドの値を左側のオペランドに代入します。C = A + B は、A + B の計算結果を C に代入します +=加算代入演算子。左オペランドに右オペランドを加算し、結果を左オペランドに代入します。C +=