C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++プログラミングで独自の代入演算子を定義すべきケースとは?

本記事では、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の値を設定しています。ところが、その変更がob2にも反映されてしまっています。このような予期しない挙動は、深刻なバグの原因となり得ます。

原因は、ユーザー定義の代入演算子が存在しないため、コンパイラが自動生成したものが使われている点にあります。自動生成された代入演算子は「シャローコピー(浅いコピー)」を行うため、右辺(RHS)のptrポインタの値がそのまま左辺(LHS)に複製されます。結果として、両オブジェクトのポインタは同一のメモリ領域を指すことになり、一方の値を変更するともう一方にも影響が及んでしまうのです。

解決方法

この問題には主に2つの対処法があります。

  • ダミーのprivateな代入演算子を宣言し、オブジェクトのコピー自体を禁止する
  • ディープコピー(深いコピー)を行う独自の代入演算子を定義する

ここでは後者の方法を採用したコードを見てみましょう。

修正後のコード例

#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には影響せず、期待どおり「50」が出力されます。

実装時の注意点として、必ず自己代入(ob1 = ob1 のような代入)のチェックを行う必要があります。自己代入チェックを怠ると、自分自身のポインタが指す値を読み込む処理と書き込む処理が競合し、現在のオブジェクトの値が意図せず破壊される可能性があります。また、より堅牢な設計を目指すなら、コピーコンストラクタやデストラクタも併せて適切に定義する「Rule of Three(3つのルール)」を意識するとよいでしょう。

  1. C++のスマートポインタとは?仕組みと使うべき場面をわかりやすく解説

    スマートポインタとは何かスマートポインタとは、C++の「生ポインタ(raw pointer/ベアポインタとも呼ばれます)」をラップするクラスのことです。生ポインタが指し示すメモリなどのリソースを自動的に管理する役割を担います。たとえば、プログラムのどこからもそのメモリ領域への参照が失われてしまった場合でも、スマートポインタは適切なタイミングでメモリを解放してくれます。この動作は、ガベージコレクタに似た働きをすると言えるでしょう。スマートポインタの主な種類C++には複数種類のスマートポインタが用意されており、標準ライブラリでは主に以下の3つが代表的です。std::unique_ptr所有権を1つ

  2. C++のfriend(フレンド)関数とは?必要になる場面と使い方を解説

    C++におけるfriend(フレンド)関数は、クラスのスコープ外で定義される関数でありながら、そのクラスのすべてのprivateメンバーおよびprotectedメンバーにアクセスする権限を持つ特別な関数です。フレンド関数のプロトタイプ宣言はクラス定義内に記述しますが、あくまで「宣言」だけであり、フレンド関数自体はそのクラスのメンバ関数にはなりません。 フレンドとして指定できるのは、通常の関数だけでなく、関数テンプレート、メンバ関数、さらにはクラスやクラステンプレートも可能です。クラスをフレンドとして指定した場合、そのクラス全体とそのすべてのメンバがフレンドとなります。 friend関数の宣言方