16章 演算子のオーバーロード

http://www.geocities.jp/ky_webid/cpp/language/017.html

名前からして凄そうなのが出てきましたね。

これは非常にややこしそうな臭いがぷんぷんします。

+や-といった演算子の振る舞いを変えてしまうんだそうです。なんだかスゴイですね。

とりあえずオブジェクト同士を足すと、保持しているメンバ変数の値を足す処理を実装してみます。

#include <iostream>

class CNum {
private:
    int m_num;
public:
    CNum (int num);
    CNum operator+(CNum& obj); // +のオーバーロード
    int getNum();
};

CNum::CNum(int num) {
    this->m_num = num;
}

CNum CNum::operator+(CNum& obj) {
    CNum tmp(this->m_num);
    tmp.m_num += obj.m_num;
    return tmp;
}

int CNum::getNum () {
    return this->m_num;
}

int main() {
    CNum num1(100);
    CNum num2(44);
    
    num1 = num1 + num2;
    
    std::cout << num1.getNum() << std::endl;
    
    return 0;
}
$ main
144

おお、すごいです。ちゃんと加算されています。

+演算子オーバーロードし、CNum型のtmp変数にm_numを足した値を格納し戻り値として返しています。

ここでひとつ問題なのが戻り値にCNum型を返さないとだめなので値返しになってしまうという点です。

これは前回のコピーコンストラクタの話になりますが、そのオブジェクトがもしデストラクタ等でメンバ変数がもっているメモリの解放処理を行っている場合、戻り値で返したオブジェクトの方で不整合が起きてしまい、とても危険なのです。

今回のケースでは特にメモリ確保を行っていないので大丈夫ですが、きちんとやるのであればコピーコンストラクタも用意すべきですね。

一応実装してみます。

#include <iostream>

class CNum {
private:
    int* m_num;
public:
    CNum (int num);
    ~CNum ();
    CNum (const CNum& obj); // コピーコンストラクタ
    CNum operator+(CNum& obj);
    int getNum();
};

CNum::CNum(int num) {
    // メモリの確保をする(本来意味の無い処理だけど)
    this->m_num = new int(num);
}

CNum::~CNum() {
    // メモリの解放をする
    delete this->m_num;
}

CNum::CNum(const CNum& obj) {
    // 確保しなおす
    this->m_num = new int(*obj.m_num);
}

CNum CNum::operator+(CNum& obj) {
    CNum tmp(*this->m_num);
    *tmp.m_num += *obj.m_num;
    return tmp;
}

int CNum::getNum () {
    return *this->m_num;
}

int main() {
    CNum num1(100);
    CNum num2(44);
    
    CNum num3 = num1 + num2;
    
    std::cout << num3.getNum() << std::endl;
    
    return 0;
}

tmp変数はoperator関数を抜けた時点でデストラクタが呼ばれ、メモリの解放をしてしまいます。

ですが、num3では値渡しによりm_numが解放されたアドレスのままコピーされるのでgetNum()でm_numに参照してもおかしくなってしまします。

なので上の処理ではコピーコンストラクタを用意し、再度値の確保を行っているというわけです。

で、おもったんですが、一番初めに書いていたような

num1 = num1 + num2;

の場合、コピーコンストラクタは呼ばれないので、結局のところ=演算子オーバーロードして値の確保をしてあげないといけませんね。

ということでそれも実装してみます。

#include <iostream>

class CNum {
private:
    int* m_num;
public:
    CNum (int num);
    ~CNum ();
    CNum (const CNum& obj); // コピーコンストラクタ
    CNum operator+(CNum& obj);
    void operator=(const CNum& obj);
    int getNum();
};

CNum::CNum(int num) {
    // メモリの確保をする(本来意味の無い処理だけど)
    this->m_num = new int(num);
}

CNum::~CNum() {
    // メモリの解放をする
    delete this->m_num;
}

CNum::CNum(const CNum& obj) {
    // 確保しなおす
    this->m_num = new int(*obj.m_num);
}

CNum CNum::operator+(CNum& obj) {
    CNum tmp(*this->m_num);
    *tmp.m_num += *obj.m_num;
    return tmp;
}

void CNum::operator=(const CNum& obj) {
    // thisはnum1でobjはtmp
    // 確保しなおす
    this->m_num = new int(*obj.m_num);
}

int CNum::getNum () {
    return *this->m_num;
}

int main() {
    CNum num1(100);
    CNum num2(44);
    
    num1 = num1 + num2;
    
    std::cout << num1.getNum() << std::endl;
    
    return 0;
}

できました。

しかしながら凄く面倒ですね。コレ。

動的に確保した値をメンバに保持しているようなクラスの場合、あまり演算子オーバーロードしない方が良い気もします。コピーコンストラクタや=演算子オーバーロードなど、やらなければならないことが一気に増えるので大変です。

そのあたりをうまい具合にサポートしてくれるような何か便利な実装方法とかあるんでしょうか?もしあるなら話は変わってきそうですが・・・。