2013年11月18日月曜日

C++ コピーコンストラクタ その2

前回、何気ないところですが、重要と書いた点の続きになります。(^_^;)
やはり賢いC++、デフォルトのコピーコンストラクタでもメンバ変数のコピーをしてくれます。
それでも、コピーコンストラクタを定義しなければいけない場合というのがあります。
メンバにポインタ変数が含まれる場合に、次のような問題が起こってしまうのです。
次のような例を見てください。

class Person {
public:
    Person() {
        m_pName = new(std::nothrow) char[32];
    }
    ~Person() {
        if (m_pName != NULL) {
            delete [] m_pName;
            m_pName = NULL;
        }
    }
            :    //このままコピーコンストラクタを用意しないでいると...   
private:
    char *m_pName;        //ここにポインタのメンバ変数があります
};

int main(void)
{
    Person *psn1 = new(std::nothrow) Person;
    //psn2 の初期化 デフォルトのコピーコンストラクタを呼び出します
    Person *psn2 = new(std::nothrow) Person(*psn1);
        :
    if (psn1 != NULL) {        delete psn1;
        psn1 = NULL;
    }
    if (psn2 != NULL) {
        delete psn2;        //2つ目のオブジェクトを削除しようとすると
        psn2 = NULL;      //実行時エラーが発生してしまいます(>_<)
    }
        :

実行例
# ./test
*** glibc detected *** ./test: double free or corruption (fasttop): 0x00728018 ***
中止

これは、デフォルトのコピーコンストラクタを使用したからなのですが、なぜ double free となってしまったのでしょうか?

実はpsn1オブジェクトの m_pName と psn2オブジェクトの m_pName のポインタが指し示す
先が同じだからです。
psn1オブジェクトを削除したときに、m_pNameの指し示す先のメモリ領域も削除されました。
そのため、psn2オブジェクトの m_pName が参照するエリアがなくなってしまい
プログラムが異常終了していたのです。

では、先ほどのサンプルにコピーコンストラクタを用意し、
エラー終了しないよう改造してみます。

#include <iostream>
#include <string.h>

class Person {
public:
    Person() {
        m_pName = new(std::nothrow) char[32];
    }
    Person(const Person &psn);  //コピーコンストラクタを宣言します
    ~Person() {
        if (m_pName != NULL) {
            delete [] m_pName;
            m_pName = NULL;
        }
    }
private:
    char *m_pName;
};

//コピーコンストラクタを定義します^^
Person::Person(const Person &psn)
{
    //人名は31文字までと勝手に決めています(^_^;)
    m_pName = new(std::nothrow) char[strnlen(psn.m_pName, 31)+1];
    strncpy(m_pName, psn.m_pName, strnlen(psn.m_pName, 32));
}

int main(void)
{
    Person *psn1 = new(std::nothrow) Person;
    //psn2 の初期化 定義したコピーコンストラクタを呼び出します
    Person *psn2 = new(std::nothrow) Person(*psn1);

    if (psn1 != NULL) {
        delete psn1;
        psn1 = NULL;
    }
    if (psn2 != NULL) {
        delete psn2;
        psn2 = NULL;
    }

    return 0;
}

ちなみに、このプログラム実行しても、何も表示しないで終了します。(^_^;)
正しく動作するようにはなりましたので、気が向いたらSetName, GetName関数
など追加して試してみてください。

今日の名言
偉大な心はしっかりしたよりどころを持っている。矮小な心は願望しか持っていない。
小さい心は不幸に慣れて大人しくなっている。偉大な心は不幸の上にそびえ立つ。
                                     ワシントン・アーヴィング

人間の偉大さは、不運に対してどのように耐えるかによって、決まるものだ。
                                     プルターク

熱中を得る方法は、自分の手がけている事柄を正しいと信じ、自分にはそれをやり遂げる
力があると信じ、積極的にそれをやり遂げたい気持ちになることである。昼のあとに夜が
くるように、ひとりでに熱中がやってくる。

何かを成し遂げようという気持ちがなければ、世間のどこへいっても頭角を現せない。
                                     デール・カーネギー

それが人生なのだ。A弦が切れることも、三本の弦で弾き終えることも。
                                ハリー・エマーソン・フォスディック