コピーコンストラクタとは引数に自分と同じクラスを持つコンストラクタである。
つまり、スタッククラスの場合、コンストラクタにスタッククラスを引数にとるコン
ストラクタを言う。
コピーコンストラクタも自分で定義しない場合は、コンパイラが自動的に作成する。
どのようなコピーコンストラクタが作成されるかというと、代入演算子と同じで、
メンバー変数を全てコピーするという原始的なコンストラクタである。
つまり代入演算子と同様、メンバー変数にポインタを持ち、デストラクタでその
ポインタが指すメモリー領域をdeleteするようなクラスを作成する場合は、コピーコンストラクタ
も自分で定義する必要がある。
コピーコンストラクタを作る前に、どのような場合にコピーコンストラクタが呼ばれるかを
考えてみる。
// Stack を引数とする関数
void func1( Stack<int> stack ){
:
:
}
// Stack の参照を引数とする関数
void func2( Stack<int>& stack ){
:
:
}
// Stack を戻り値とする関数
Stack<int> func3( ){
Stack<int> stack = 10; // Stack(int)の呼び出し
return stack; // コピーコンストラクタの呼び出し
// 自動変数stackを呼び出し元のテンポラリ領域にコピーする
}
// テストアプリ
void main(){
Stack<int> stack1; // デフォルトコンストラクタの呼び出し
Stack<int> stack2(10); // Stack(int)の呼び出し
Stack<int> stack3 = 20; // Stack(int)の呼び出し
Stack<int> stack4 = stack1; // コピーコンストラクタの呼び出し
stack3 = stack2; // 代入演算子(=)の呼び出し
func1( stack2 ); // コピーコンストラクタの呼びだし
// 実引数stack2をfunc1の仮引数にコピーする
func2( stack2 ); // 参照なのでコピーコンストラクタは呼ばれない
Stack<int> stack5 = func3();
// func3の戻り値はテンポラリ領域にコピーされた後、
// stack5 にコピーされる。つまり、コピーコンストラクタ
// は2回呼び出される。コンパイラによっては、このコピー
// 操作を1回で済ませるよう最適化する場合もある
}
|
コピーコンストラクタは以下のように定義します。
class TestClass{
TestClass( const TestClass& src ){
:
:
}
};
|
コピーコンストラクタの引数は、constな参照型でなければなりません。もし、参照型
でなければ、コピーコンストラクタを呼び出すときに、コピー元がコピーされて渡される
ことになります。つまりコピーコンストラクタを呼び出すためにはコピーコンストラクタを
呼ばなければいけないと言う、奇妙な現象が起きてしまうからです。したがって、コピー
コンストラクタは引数がコピーされないよう、参照型にする必要があります。また、
コピーコンストラクタがコピー元を触らないことを保証するため、「const」を付けます。
自分でコピーコンストラクタを作成したスタッククラスは以下のようになります。
変更点はアンダーラインの部分です。
/////////////////////////////////////////////////////////////////////////////
//
// テンプレートスタッククラス(動的メモリ割当版)定義
//
// 2重取り込みを防止する
#ifndef STACK_H
#define STACK_H
#include<process.h>
#include<iostream>
using namespace std;
//////////////////////////////////////////////////////////////////////////////
// スタッククラス
template<class Type>
class Stack {
public:
// コンストラクタ:
// Stack オブジェクトが定義された時に自動的に呼び出される
Stack( int sz = 100 ){
// コンストラクタ引数を省略すると、スタックサイズは100となる
// スタックサイズを保存する
size = sz ;
// スタックの実体(size個のType配列)を自由記憶上に割り当てる
stack = new Type[size] ;
// スタックポインタを初期化する
sp = 0 ;
}
// コピーコンストラクタ
Stack( const Stack<Type>& src ){
copy( src ) ;
}
// デストラクタ:
// Stack オブジェクトが削除される時に自動的に呼び出される
~Stack(){
//スタック本体を削除する
delete [ ] stack ;
}
// プッシュ
void push( const Type& value ){
// オーバーフローのチェック
if( sp >= size ){
cerr << "stack overflow" << endl ;
exit( 1 ) ;
}
*( stack + sp ) = value ;
sp++ ;
}
// ポップ
void pop( Type* p_value ){
// アンダーフローのチェック
if( sp <= 0 ){
cerr << "stack underflow" << endl;
exit( 1 ) ;
}
sp--;
*p_value = *( stack + sp );
}
// 現在のスタック長を獲得する
int get_length(){
return sp;
}
// 指定位置(スタックトップからのオフセット)のスタック要素を覗き見る
void peek( Type* p_value, int offset = 0 ){
// オフセットのチェック
if( offset >= sp ){
cerr << "stack underflow" << endl ;
exit( 1 ) ;
}
// 指定位置の要素をコピーする
*p_value = *( stack + sp - offset - 1 );
}
// 代入演算子
Stack<Type>& operator=( const Stack<Type>& src ){
// 自己代入でなければ
if( &src != this ){
// 現在自分が保有しているスタックを削除する
delete [] stack;
// コピー元の配列をコピーする
copy( src );
}
return *this;
}
private:
// 代入演算子とコピーコンストラクタから呼ばれる
// コピーもとのメンバー変数size、spをコピーし、
// 新しい配列を割り当て、配列の中身をコピーする
void copy( const Stack<Type>& src ){
// サイズをコピーする
size = src.size;
// スタックポインタをコピーする
sp = src.sp;
// サイズ分の配列を作る
stack = new Type[size];
// 配列の中身をコピーする
for( int i = 0 ; i < size ; i++ )
stack[i] = src.stack[i];
}
////////////////////////////////////////////////////
// 管理情報
// スタックポインタ:次にプッシュする位置
int sp;
// スタックサイズ
int size;
// スタック
Type *stack;
};
#endif // STACK_H
|
コピー操作は、代入演算子とコピーコンストラクタの両方から呼ばれるため、
ヘルパー関数として、privateなcopy関数を用意し、これを呼ぶようにした。