読者です 読者をやめる 読者になる 読者になる

コピーコンストラクタ

C++ Sample

コピーコンストラクタは、オブジェクトをコピーするときに呼ばれるコンストラクタ・・・。

恒例のclass_profileを作成する・・・。

profile.hは、

// クラスclass_profileの定義
class class_profile{ // 簡易名簿

  // privateメンバ
  private: // このクラスからのみアクセス可.

    // privateメンバ変数.
    char *name_; // char型ポインタname_.
    int age_; // int型変数age_.
    char *address_; // char型ポインタaddress_.

  // publicメンバ
  public: // 外部からアクセス可.

    // publicメンバ関数.
    class_profile(); // コンストラクタclass_profile().
    ~class_profile(); // デストラクタ~class_profile().
    void set_profile(const char *name, const int age, const char *address); // name, age, addressをセットす>るメンバ関数set_profile.
    const char *get_name(); // nameを取得するメンバ関数get_name().
    const int get_age(); // ageを取得するメンバ関数get_age().
    const char *get_address(); // addressを取得するメンバ関数get_address().

};

こんな感じで、set_profileで3つ一気にセット、get_name, get_age, get_addressで1つずつ取得できる・・・。
(1つずつセットするメンバ関数は面倒だし、今回は使わないのでカット・・・。)

profile.cppのコンストラクタ、デストラクタは、

// ヘッダのインクルード
// 既定のヘッダ
#include <cstdio> // C標準入出力
#include <iostream> // C++標準入出力
#include <cstring> // C文字列処理
// 独自のヘッダ
#include "profile.h" // クラスclass_profile.

// コンストラクタclass_profile()
class_profile::class_profile(){

  // メンバの初期化.
  name_ = NULL; // name_をNULLに初期化.
  age_ = 0; // age_を0に初期化.
  address_ = NULL; // address_をNULLに初期化.

  // コンストラクタが呼ばれたことを示す.
  std::cout << "constructor" << std::endl; // "constructor"と出力.

}

// デストラクタ~class_profile()
class_profile::~class_profile(){

  // メンバの終了処理.
  if (name_ != NULL){ // name_がNULLでなければ.
    printf("before delete [] name_ = %p\n", name_); // name_に格納されたアドレスを出力.
    delete [] name_; // name_のメモリを解放.
    name_ = NULL; // name_にNULLをセット.
  }
  age_ = 0; // age_を0にセット.
  if (address_ != NULL){ // address_がNULLでなければ.
    printf("before delete [] address_ = %p\n", address_); // address_に格納されたアドレスを出力.
    delete [] address_; // address_のメモリを解放.
    address_ = NULL; // address_にNULLをセット.
  }

  // デストラクタが呼ばれたことを示す.
  std::cout << "destructor" << std::endl; // "destructor"と出力.

}

コンストラクタで初期化、デストラクタでポインタのメモリを解放したり、0にしたりしている・・・。
また、name_, address_が解放される前にアドレスを出力している・・・。
メンバ関数は、

// メンバ関数set_profile(const char *name, const int age, const char *address)
void class_profile::set_profile(const char *name, const int age, const char *address){

  // 変数の宣言
  int name_len; // nameの長さint型name_len.
  int address_len; // addressの長さint型address_len.

  // name, addressの長さを取得.
  name_len = strlen(name); // strlenでnameの長さを取得し, name_lenに格納.
  address_len = strlen(address); // strlenでaddressの長さを取得し, address_lenに格納.

  // メモリの確保.
  name_ = new char[name_len + 1]; // 長さname_len + 1のchar型メモリを確保し, ポインタをname_に格納.
  address_ = new char[address_len + 1]; // 長さaddress_len + 1のchar型メモリを確保し, ポインタをaddress_に格納.

  // データのコピー.
  strcpy(name_, name); // strcpyでname_にnameをコピー.
  age_ = age; // age_にageの値を代入.
  strcpy(address_, address); // strcpyでaddress_にaddressをコピー.

  // set_profileが呼ばれたことを示す.
  std::cout << "set_profile" << std::endl; // "set_profile"を出力.

}

// メンバ関数get_name()
const char *class_profile::get_name(){

  // get_nameが呼ばれたことを示す.
  std::cout << "get_name" << std::endl; // "get_name"を出力.

  // name_を返す.
  return name_; // name_を返す.

}

// メンバ関数get_age()
const int class_profile::get_age(){

  // get_ageが呼ばれたことを示す.
  std::cout << "get_age" << std::endl; // "get_age"を出力.

  // age_を返す.
  return age_; // age_を返す.

}

// メンバ関数get_address()
const char *class_profile::get_address(){

  // get_addressが呼ばれたことを示す.
  std::cout << "get_address" << std::endl; // "get_address"を出力.

  // address_を返す.
  return address_; // address_を返す.

}

set_profileでもらった引数の文字列長をstrlenで取得し、newでメモリを確保、strcpyでメンバにコピー・・・。
age_は数値なのでそのまま代入・・・。
get_name, get_age, get_addressで返すデータはみんなconstにして変更不可に・・・。

コンストラクタ、デストラクタ、メンバ関数、全てで関数名を出力し、どの関数が呼ばれたかを示している・・・。

main.cppは、

// ヘッダのインクルード
// 既定のヘッダ
#include <iostream> // C++標準入出力
// 独自のヘッダ
#include "profile.h" // クラスclass_profile

// main関数の定義
int main(void){

  // オブジェクトの宣言
  class_profile obj1; // class_profileオブジェクトobj1

  // メンバのセット.
  obj1.set_profile("Taro", 20, "Tokyo"); // obj1.set_profileで"Taro", 20, "Tokyo"をセット.

  // メンバを取得し, 出力.
  std::cout << "obj1.get_name() = " << obj1.get_name() << std::endl; // obj1.get_nameでname_を取得し, 出力.
  std::cout << "obj1.get_age() = " << obj1.get_age() << std::endl; // obj1.get_ageでage_を取得し, 出力.
  std::cout << "obj1.get_address() = " << obj1.get_address() << std::endl; // obj1.get_addressでaddress_を取得し, 出力.

  // さらなるオブジェクトの宣言
  class_profile obj2 = obj1; // class_profileオブジェクトobj2をobj1で初期化.

  // メンバを取得し, 出力.
  std::cout << "obj2.get_name() = " << obj2.get_name() << std::endl; // obj2.get_nameでname_を取得し, 出力.
  std::cout << "obj2.get_age() = " << obj2.get_age() << std::endl; // obj2.get_ageでage_を取得し, 出力.
  std::cout << "obj2.get_address() = " << obj2.get_address() << std::endl; // obj2.get_addressでaddress_を取得し, 出力.

  // プログラムの終了
  return 0;

}

obj1はset_profileでメンバをセットし、get_name, get_age, get_addressで各メンバを取得し、出力している・・・。

さて、obj2は見慣れないと思う・・・。
オブジェクトで初期化している・・・。
obj2をobj1で初期化しているわけだ・・・。
この場合、obj1と同じデータがobj2にビット単位でコピーされるのだが、そうなるとちょっと問題がある・・・。
それは実行してみればわかる・・・。

実行すると、

$ g++ -o main main.cpp profile.cpp
$ ./main
constructor
set_profile
get_name
obj1.get_name() = Taro
get_age
obj1.get_age() = 20
get_address
obj1.get_address() = Tokyo
get_name
obj2.get_name() = Taro
get_age
obj2.get_age() = 20
get_address
obj2.get_address() = Tokyo
before delete [] name_ = 0xaed010
before delete [] address_ = 0xaed030
destructor
before delete [] name_ = 0xaed010
before delete [] address_ = 0xaed030
destructor
$

2つのオブジェクトがあるわけだから、2つデストラクタが呼ばれている・・・。
どちらがどちらかわからないが、name_のアドレスも2つとも同じ、address_のアドレスも2つとも同じ・・・。
ポインタもコピーをするのだから当然だが、そうするとこのように同じアドレスのメモリが二重解放されてしまう・・・。

意外にもSEGVなどは出ない・・・。
しかし、

無為空間 |MALLOC_CHECK_

MALLOC_CHECK_=3にしてみたら、

$ export MALLOC_CHECK_=3
$ ./main
constructor
set_profile
get_name
obj1.get_name() = Taro
get_age
obj1.get_age() = 20
get_address
obj1.get_address() = Tokyo
get_name
obj2.get_name() = Taro
get_age
obj2.get_age() = 20
get_address
obj2.get_address() = Tokyo
before delete [] name_ = 0x2027010
before delete [] address_ = 0x2027030
destructor
before delete [] name_ = 0x2027010
*** Error in `./main': free(): invalid pointer: 0x0000000002027010 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x7c5ee)[0x7f0843fcd5ee]
./main[0x400de8]
./main[0x400cc5]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7f0843f72b15]
./main[0x400a59]
======= Memory map: ========
00400000-00402000 r-xp 00000000 ca:03 203882626                          /home/bg1/project/cloud/github.com/Sample/cpp/copy_constructor/copy_constructor/src/copy_constructor/main
00601000-00602000 r--p 00001000 ca:03 203882626                          /home/bg1/project/cloud/github.com/Sample/cpp/copy_constructor/copy_constructor/src/copy_constructor/main
00602000-00603000 rw-p 00002000 ca:03 203882626                          /home/bg1/project/cloud/github.com/Sample/cpp/copy_constructor/copy_constructor/src/copy_constructor/main
02027000-02048000 rw-p 00000000 00:00 0                                  [heap]
7f0843f51000-7f0844108000 r-xp 00000000 ca:03 152805458                  /usr/lib64/libc-2.17.so
7f0844108000-7f0844308000 ---p 001b7000 ca:03 152805458                  /usr/lib64/libc-2.17.so
7f0844308000-7f084430c000 r--p 001b7000 ca:03 152805458                  /usr/lib64/libc-2.17.so
7f084430c000-7f084430e000 rw-p 001bb000 ca:03 152805458                  /usr/lib64/libc-2.17.so
7f084430e000-7f0844313000 rw-p 00000000 00:00 0
7f0844313000-7f0844328000 r-xp 00000000 ca:03 152806609                  /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f0844328000-7f0844527000 ---p 00015000 ca:03 152806609                  /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f0844527000-7f0844528000 r--p 00014000 ca:03 152806609                  /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f0844528000-7f0844529000 rw-p 00015000 ca:03 152806609                  /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f0844529000-7f084462a000 r-xp 00000000 ca:03 152804739                  /usr/lib64/libm-2.17.so
7f084462a000-7f0844829000 ---p 00101000 ca:03 152804739                  /usr/lib64/libm-2.17.so
7f0844829000-7f084482a000 r--p 00100000 ca:03 152804739                  /usr/lib64/libm-2.17.so
7f084482a000-7f084482b000 rw-p 00101000 ca:03 152804739                  /usr/lib64/libm-2.17.so
7f084482b000-7f0844914000 r-xp 00000000 ca:03 152805340                  /usr/lib64/libstdc++.so.6.0.19
7f0844914000-7f0844b14000 ---p 000e9000 ca:03 152805340                  /usr/lib64/libstdc++.so.6.0.19
7f0844b14000-7f0844b1c000 r--p 000e9000 ca:03 152805340                  /usr/lib64/libstdc++.so.6.0.19
7f0844b1c000-7f0844b1e000 rw-p 000f1000 ca:03 152805340                  /usr/lib64/libstdc++.so.6.0.19
7f0844b1e000-7f0844b33000 rw-p 00000000 00:00 0
7f0844b33000-7f0844b54000 r-xp 00000000 ca:03 152804505                  /usr/lib64/ld-2.17.so
7f0844d48000-7f0844d4d000 rw-p 00000000 00:00 0
7f0844d51000-7f0844d54000 rw-p 00000000 00:00 0
7f0844d54000-7f0844d55000 r--p 00021000 ca:03 152804505                  /usr/lib64/ld-2.17.so
7f0844d55000-7f0844d56000 rw-p 00022000 ca:03 152804505                  /usr/lib64/ld-2.17.so
7f0844d56000-7f0844d57000 rw-p 00000000 00:00 0
7fff44d31000-7fff44d46000 rw-p 00000000 00:00 0                          [stack]
7fff44dd3000-7fff44dd5000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
中止
$

invalid pointerだそうで・・・。
これではメモリを破壊されるのでまずい・・・。

そこでコピーコンストラクタである・・・。

class_profile obj2 = obj1;

このようなコピーが発生するような状況の時、コピーコンストラクタは呼ばれ、ポインタの場合の正しい対処を書くことができる・・・。

ちなみにコピーコンストラクタが呼ばれる状況は、上記以外に、

class_profile obj2(obj1);

や、関数の引数にobj1のようなオブジェクトをそのまま渡した場合、関数の戻り値にobj1のようなオブジェクトをそのまま返した場合などがある・・・。

obj2 = obj1;

のような代入はコピーではあるがコピーコンストラクタの対象外で、この場合コピー代入演算子オーバーロードが必要なので注意・・・。

というわけで、

コピーコンストラクタを定義・・・。

クラス名(const クラス名 &オブジェクト名)

こんな感じで、引数に自分自身のクラスオブジェクトの参照をconstで指定・・・。

で定義は、

objの各メンバを使って、set_profileと同様にメンバをセットしている・・・。
ここでnewで新たにメモリ確保しているので、コピー元とコピー先でポインタが同じになることはない・・・。
set_profileを呼べば楽に済むと思うだろうが、objのほうにメンバがセットされているか1つずつチェックしながらコピーできないので、呼ばずに自前でコピーしている・・・。

これで実行すると、

$ g++ -o main main.cpp profile.cpp
$ MALLOC_CHECK_=3 ./main
constructor
set_profile
get_name
obj1.get_name() = Taro
get_age
obj1.get_age() = 20
get_address
obj1.get_address() = Tokyo
copy_constructor
get_name
obj2.get_name() = Taro
get_age
obj2.get_age() = 20
get_address
obj2.get_address() = Tokyo
before delete [] name_ = 0x7b8050
before delete [] address_ = 0x7b8070
destructor
before delete [] name_ = 0x7b8010
before delete [] address_ = 0x7b8030
destructor
$

エラーは出なくなった・・・。アドレスもそれぞれ違うものになっている・・・。
(MALLOC_CHECK_はこんな風に実行時に指定することもできる・・・。)

Sample/cpp/copy_constructor/copy_constructor/src/copy_constructor at master · bg1bgst333/Sample · GitHub