書籍転載
文法からはじめるプログラミング言語Microsoft Visual C++入門

C++のクラスをマスターしよう(後編)
―― 第10章 クラス〜オブジェクト指向プログラミング(中編) ――

WINGSプロジェクト 矢吹 太朗(監修 山田 祥寛)
2010/06/02
Page1 Page2

本コーナーは、日経BPソフトプレス発行の書籍『文法からはじめるプログラミング言語Microsoft Visual C++入門』の中から、特にInsider.NET読者に有用だと考えられる章や個所をInsider.NET編集部が選び、同社の許可を得て転載したものです。基本的に元の文章をそのまま転載していますが、レイアウト上の理由などで文章の記述を変更している部分(例:「上の図」など)や、図の位置などを本サイトのデザインに合わせている部分が若干ありますので、ご了承ください。『文法からはじめるプログラミング言語Microsoft Visual C++入門』の詳細は「目次情報ページ」もしくは日経BPソフトプレスのサイトをご覧ください。

ご注意:本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。

10.1.10 コピーコンストラクタ

 あるオブジェクトをコピーして新しいオブジェクトを作る際には、コピーコンストラクタを利用します。デストラクタの場合と同様で、フリーストア上のオブジェクトを保持していない場合には、コンパイラが自動生成するコピーコンストラクタで十分です。

 次のようなクラスA、Bを考えます。Aオブジェクトa1は内部にBオブジェクトを持っています。a1をコピーして新しいオブジェクトa2を作るコピーコンストラクタは、a1.idだけでなく、a1.bやa1.b.idもコピーしなければなりません。コンパイラが自動生成するコピーコンストラクタは、オブジェクトを再帰的にコピーすることでこのタスクを実行します。

#include <iostream>
using namespace std;

struct B
{
  int id;
};

struct A
{
  int id;
  B b;
};

int main()
{
  A a1;
  a1.id=10;
  a1.b.id=11; //a1のメンバbのフィールドidを設定

  //a1をコピーして新しいオブジェクトa2を作る
  A a2=a1; //A a2(a1); //でもよい

  //コピーされたことの確認
  cout<<a2.id<<endl; //出力値: 10
  cout<<a2.b.id<<endl; //出力値: 11

  //a1とa2が同じオブジェクトではないことの確認
  a1.id=20; //a1のフィールドや
  a1.b.id=22; //a1.bのフィールドを変更しても
  cout<<a2.id<<endl; //出力値: 10(a2.idは変わっていない)
  cout<<a2.b.id<<endl; //出力値: 11(a2.b.idは変わっていない)
}
[サンプル]10-copyconstructor1.cpp

 このコードでは、a1.idの値を10に、a1.b.xの値を11にして、a1をコピーして新しいオブジェクトa2を作っています。a2.idやa2.b.idが元の値と同じであることと、a1.idやa1.b.idを変更してもa2.idやa2.b.idは変わっていないことから、a1とa2、a1.bとa2.bは同じオブジェクトではない、つまり正しくコピーされたことがわかります。

 しかし、図10-4の右側のように、メンバとしてポインタを持っていて、そのポインタが指すオブジェクトもコピーしたい場合には、自動生成されるコピーコンストラクタは役に立ちません*6。ポインタの値(アドレス)がコピーされるだけなので、新しいポインタも同じオブジェクトを指すからです。

*6 アドレスをコピーしたい場合には問題ありません。これは代入演算子(後述)の場合にもあてはまります。

図10-4 既定のコピーコンストラクタでうまくいく例とうまくいかない例

 ポインタが指すオブジェクトも複製したいような場合には、コピーコンストラクタを自分で定義しなければなりません。コピーコンストラクタは次のように定義します(宣言と定義を分けることもできます)。rhsはright-hand sideの略で右辺を表します。A a1=a2のように書いたときのrhsはa2です。

クラス名(const クラス名& rhs) : コンストラクタ初期化子リスト
{
  文
}
[構文]コピーコンストラクタの定義

 コピーコンストラクタはすべてのフィールドをコピーしなければなりません。ここでコピーすべきなのはidとpBが指すオブジェクトつまり*pBです。idをコピーするのは簡単で、id(rhs.id)という初期化子を書けばよいでしょう。この初期化子によって、新しいAオブジェクトのidが、元のオブジェクト(rhs)のidと同じ値になります。同じようにpB(rhs.pB)としても、rhs.pBの値つまりオブジェクトのアドレスがコピーされるだけで、オブジェクト自体はコピーされません。pBが指すオブジェクトをコピーするためには、新しいBオブジェクトを作り、そのフィールドidを元のオブジェクトのフィールドidつまりrhs.pB->idと同じにしなければなりません。

 次のようなコピーコンストラクタで目的は達成できます。

#include <iostream>
using namespace std;

struct B
{
  int id;
};

struct A
{
  int id;
  B* pB;

  //Bオブジェクトを生成するコンストラクタ
  A() : pB(new B) {}

  //コピーコンストラクタ
  A(const A& rhs) : id(rhs.id), pB(new B) { pB->id=rhs.pB->id; }

  ~A() { delete pB; }
};

int main()
{
  A a1;
  a1.id=10;
  a1.pB->id=11;

  A a2=a1; //コピーコンストラクタ

  //コピーされたことの確認
  cout<<a2.id<<endl; //出力値: 10
  cout<<a2.pB->id<<endl; //出力値: 11

  //a1とa2が同じオブジェクトではないことの確認
  a1.id=20; //a1のフィールドや
  a1.pB->id=22; //*a1.pBのフィールドを変更しても
  cout<<a2.id<<endl; //出力値: 10(a2.idは変わっていない)
  cout<<a2.pB->id<<endl; //出力値: 11(a2.pB->idは変わっていない)
}
[サンプル]10-copyconstructor2.cpp

10.1.11 暗黙の変換

 引数の数が1つのコンストラクタは、暗黙の変換も定義します。暗黙の変換によって、オブジェクトを初期化するための記述を次のように単純にすることができます。

#include <iostream>
using namespace std;

struct A
{
  int x;
  A(int x) : x(x) { cout<<"A(int x) is called.\n"; };
};

int main()
{
  A a=5; //A a(5);と解釈される。
  //出力値:A(int x) is called.
}
[サンプル]10-explicit1.cpp

 このコードでは、A a=5;としてAオブジェクトを生成しようとしていますが、5はAオブジェクトではなくint型のリテラルなので、前項で紹介したコピーコンストラクタは使われません。この記述は、A a(5);と解釈され、コンストラクタA(int x)が呼ばれます。これが暗黙の変換です。3.4.1項で紹介したように、std::stringオブジェクトをstring str("Hello World!");ではなくstring str="Hello World!";として生成できるのも暗黙の変換のおかげです。

 暗黙の変換は便利な機能ですが、プログラマが予期しない場所で起こる危険もあります。暗黙の変換が起こらないようにするためには、次のようにキーワードexplicitを付けてコンストラクタを宣言します。

#include <iostream>
using namespace std;

struct A
{
  int x;
  //暗黙の型変換を禁止する
  explicit A(int x) : x(x) { cout<<"A(int x) is called.\n"; };
};

int main()
{
  A a=5; //エラー
}
[サンプル]10-explicit2.cpp


 INDEX
  [書籍転載]文法からはじめるプログラミング言語Microsoft Visual C++入門
  C++のクラスをマスターしよう(後編)
  1.コピーコンストラクタ/暗黙の変換
    2.代入演算子/標準コンテナの利用/テンプレートクラス

インデックス・ページヘ 「文法からはじめるプログラミング言語Microsoft Visual C++入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間