ページ 1 / 1
コピーコンストラクタについて
Posted: 2008年10月12日(日) 20:08
by 大工
スタックの内容を統合するというプログラムなんですが,コピーコンストラクタが使われている場面がよくわからないので質問させてください.
添付のプログラムでコピーコンストラクタの部分がないとエラーになると授業で習ったのですが,見た感じはコピーコンストラクタがいらなくても別に異常なく動作するように感じます.
なぜなら
ex1:
CLASS ob1;
CLASS ob2 = ob1;
というようなコピーコンストラクタを用いた操作をplus関数内で行っていないからです.また,main関数内での代入演算子がex1の様な動作をしているのかと調べてみてもoperator=が動いてるだけでコピーコンストラクタではありませんでした.
コピーコンストラクタがplus(stack s1, stack s2)の引数に使われているのは理解しています.plus関数の引数以外でコピーコンストラクタがどこで効いてくるのでしょうか?
stack(int sz)0xbfde5304
stack(int sz)0xbfde52f8
stack(int sz)0xbfde52ec
------------
stack(const stack& s)0xbfde52d4
stack(const stack& s)0xbfde52e0
stack(int sz)0xbfde5284
stack(int sz)0xbfde52c8
~stack()0xbfde5284
operator=(const stack& s)0xbfde52ec
~stack()0xbfde52c8
~stack()0xbfde52e0
~stack()0xbfde52d4
max=5, sp=3, data=(1 3 5 )
max=5, sp=3, data=(2 4 6 )
max=10, sp=6, data=(1 3 5 2 4 6 )
~stack()0xbfde52ec
~stack()0xbfde52f8
~stack()0xbfde5304
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 20:52
by Justy
>plus関数の引数以外でコピーコンストラクタがどこで効いてくるのでしょうか?
plus(stack s1, stack s2)の戻り値ですかね。
最適化されると、消えてしまうかもしれませんが。
>添付のプログラムでコピーコンストラクタの部分がないとエラーになると
>授業で習ったのですが,見た感じはコピーコンストラクタがいらなくても
>別に異常なく動作するように感じます.
stackクラスのコピーコンストラクタを明示的に定義しなくても異常なく動作する、
という意味でしょうか?
まぁ、コンパイルは通りますよ。定義しなくても暗黙的にコピーコンストラクタは
作られますから。
ただ、オブジェクトのコピーが発生した段階でそのクラスは正常には作動しなくなります。
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 21:25
by 大工
>stackクラスのコピーコンストラクタを明示的に定義しなくても異常なく動作する、
>という意味でしょうか?
その通りです.
と,いうことはplus関数内のsはmain関数に戻ってくる際に(return s; の時点で)コピーコンストラクタが暗に呼び出されそれを代入演算子によって入力しているからコピーコンストラクタが無い場合,もうすでに開放した無効な領域をさらに開放しようとしてエラーとなるということでしょうか?
なにかしら関数をClass func();と言うように定義していると戻り値ではコピーコンストラクタが呼ばれている?Class& func();では呼ばれない?
よろしくお願いします.
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 21:58
by Justy
>と,いうことはplus関数内のsはmain関数に戻ってくる際に
>~略~
>もうすでに開放した無効な領域をさらに開放しようとしてエラーとなるということでしょうか?
エラーになるかどうは環境にもよりますが、そういうことになります。
>なにかしら関数をClass func();と言うように定義していると戻り値ではコピーコンストラクタが呼ばれている
基本的には呼ばれる、と考えて下さい。
ただ、コンパイラによって最適化・・・戻り値の最適化(RVO)が働くことあります。
ttp://www.kmonos.net/alang/cpp/glossary.html#RVO
ttp://ml.tietew.jp/cppll/cppll/thread_articles/12750#ar12750
その為省略されることもありますので、呼ばれることを前提にして組んでしまうと
それはそれで問題になるかもしれません。
>Class& func();では呼ばれない?
これは、
[color=#d0d0ff" face="monospace] stack& hoge1()
{
static hoge h(5);
return h;
}[/color]
とか
[color=#d0d0ff" face="monospace] stack& hoge2(stack& h)
{
return h;
}
[/color]
という感じのを想定していますか?
これらでしたら、関数を使うだけではコピーコンストラクタは呼ばれません。
でも、もちろん、
[color=#d0d0ff" face="monospace]
stack s1 = hoge1();
stack s2 = hoge2(s1);
[/color]
とかしたら、呼ばれてしまいますが。
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 22:02
by 大工
??????????????
戻り値をなんらかの変数に入力しようとしたらアンパーサンドをつけようがつけまいが一緒ってことでしょうか?
なんだか混乱してきました.
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 22:07
by Justy
>戻り値をなんらかの変数に入力しようとしたらアンパーサンドをつけようがつけまいが一緒ってことでしょうか?
混乱させて済みません。
上の例のように戻り値を変数に入力した際によばれるのは s1、s2のコピーコンストラクタが
働くからです。
この hoge1/hoge2関数自体は使うことでコピーコンストラクタが呼ばれることはありません。
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 22:25
by 大工
上の例のように戻り値を変数に入力した際によばれるのは s1、s2のコピーコンストラクタが
働くからです。
この hoge1/hoge2関数自体は使うことでコピーコンストラクタが呼ばれることはありません。
なるほど.ですが,質問の中にある実行結果でoperator=のオーバーロードが呼ばれていますが,コピーコンストラクタとは別に動いてる
ということでしょうか?ちゃんとコンストラクタが実行されたらコメントが出るようにしてるのに出てません・・・・・よね?
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 22:52
by Justy
>質問の中にある実行結果でoperator=のオーバーロードが呼ばれていますが,コピーコンストラクタとは別に動いてる
別です。
>ちゃんとコンストラクタが実行されたらコメントが出るようにしてるのに出てません・
ですから、先も書きましたように戻り値の最適化が働くとコピーコンストラクタが
省略されることがあるので、多分そのケースに該当するのでしょう。
ちなみにうちの環境でも同じようになっています。
最適化なしで実行するとこうなります。
stack(int sz)0013FF50 // s1の生成
stack(int sz)0013FF3C // s2の生成
stack(int sz)0013FF28 // sの生成
------------
stack(const stack& s)0013FE08 // s2のplusの引数の為のコピー
stack(const stack& s)0013FDFC // s1のplusの引数の為のコピー
stack(int sz)0013FDC8 // plus関数内でのローカル変数 tmp生成
stack(int sz)0013FDB4 // plus関数内でのローカル変数 s生成
stack(const stack& s)0013FE54 // plus関数の戻り値としてコピー
~stack()0013FDB4 // ローカル変数 s の破棄
~stack()0013FDC8 // ローカル変数 tmpの破棄
~stack()0013FDFC // ローカル変数 s1 の破棄
~stack()0013FE08 // ローカル変数 s2 の破棄
operator=(const stack& s)0013FF28 // plus関数の戻り値を mainの sへのコピー
~stack()0013FE54 // plus関数の戻り値を破棄
max=5, sp=3, data=(1 3 5 )
max=5, sp=3, data=(2 4 6 )
max=10, sp=6, data=(1 3 5 2 4 6 )
~stack()0013FF28 // sの破棄
~stack()0013FF3C // s2の破棄
~stack()0013FF50 // s1の破棄
最適化をかけるとこうなります。
stack(int sz)0013FF4C
stack(int sz)0013FF40
stack(int sz)0013FF58
------------
stack(const stack& s)0013FF28 // s2のplusの引数の為のコピー
stack(const stack& s)0013FF1C // s1のplusの引数の為のコピー
stack(int sz)0013FEFC // plus関数内でのローカル変数 tmp生成
stack(int sz)0013FF64 // plus関数内でのローカル変数 s生成
~stack()0013FEFC // ローカル変数 tmpの破棄
~stack()0013FF1C // ローカル変数 s1 の破棄
~stack()0013FF28 // ローカル変数 s2 の破棄
operator=(const stack& s)0013FF58 // plus関数の戻り値(plus関数内でのローカル変数 s)を mainの sへのコピー
~stack()0013FF64 // plus関数の戻り値(plus関数内でのローカル変数 s)を破棄
max=5, sp=3, data=(1 3 5 )
max=5, sp=3, data=(2 4 6 )
max=10, sp=6, data=(1 3 5 2 4 6 )
~stack()0013FF58
~stack()0013FF40
~stack()0013FF4C
最適化をかけると見事にplus関数の戻り値としてのコピー(コンストラクタ)がなくなり、
代わりに plus関数内部で作成した変数 sをそのまま main関数の sに代入しているのが
わかりますでしょうか。
これが戻り値の最適化です。
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 23:32
by 大工
むむ!コピーコンストラクタがどのように使われているか分かった気がします.
まだちょっと質問があるのでおねがいします><.
stack(const stack& s)0013FE54 // plus関数の戻り値としてコピー
これは s を plus関数の戻り値としてコピーするという理解で良いでしょうか?
あと,
operator=(const stack& s)0013FF58 // plus関数の戻り値(plus関数内でのローカル変数 s)を mainの sへのコピー
~stack()0013FF64 // plus関数の戻り値(plus関数内でのローカル変数 s)を破棄
operator=をオーバーロードしているので,各々のオブジェクトにデータが入っている
(つまり,ob1 = ob2; とするとob1はob2のデータがあるメモリをみているのではなく,ob1がデータをもっているという意味です)
ように設計したので実際のところコピーコンストラクタはいらないんじゃ?という疑問が出てきました.
Re:コピーコンストラクタについて
Posted: 2008年10月12日(日) 23:55
by Justy
>これは s を plus関数の戻り値としてコピーするという理解で良いでしょうか?
OKです。
>ように設計したので実際のところコピーコンストラクタはいらないんじゃ
operator=があるからといって、コピーコンストラクタを削除すると、
[color=#d0d0ff" face="monospace] {
stack st1(5);
stack st2a(a1); // *
stack st2b = a1; // *
}
[/color]
このコードを実行すると st1と st2a、st2bのメンバ dataは全て同じになってしまい、
結果デストラクタで多重解放になりますよ。
Re:コピーコンストラクタについて
Posted: 2008年10月13日(月) 00:10
by 大工
それもそうですが,さっき言いたかったのはmain関数内のs = plus(s1, s2);での話です.
言葉たらずですみません・・・
Re:コピーコンストラクタについて
Posted: 2008年10月13日(月) 00:19
by Justy
>それもそうですが,さっき言いたかったのはmain関数内のs = plus(s1, s2);での話です.
??
えーと、ちょっとご質問の意図が読みとれなかったので、お訊きしますが、
「main関数内のs = plus(s1, s2);」でコピーコンストラクタはいらないと思った、
というのはどういうことでしょう??
Re:コピーコンストラクタについて
Posted: 2008年10月13日(月) 00:37
by 大工
えとですね・・・・・・・
operator=をオーバーロードしたので同じメモリを参照することはありませんよね?
だからコピーコンストラクタを明示的に定義しなくてもoperator=できちんと値が入力されると思ったからです....
Re:コピーコンストラクタについて
Posted: 2008年10月13日(月) 00:54
by Justy
>operator=をオーバーロードしたので同じメモリを参照することはありませんよね?
なるほど。
えーとですね。
「main関数内のs = plus(s1, s2);」の戻り値の代入部分に関しては
既に作られた変数 sへの代入になり、operator=()が呼ばれますので
たしかに同じメモリを参照することはありません。
従って、今回のプログラムの内容ですと、plus関数の引数のところさえ
修正すれば、仰るとおりコピーコンストラクタは不要、と考えてもいいのかもしれません。
とはいえ、クラスの設計的にはやっぱり必要だと思いますよ。
Re:コピーコンストラクタについて
Posted: 2008年10月13日(月) 01:02
by 大工
なるほど,ありがとうございます.
代入演算子とコピーコンストラクタは絶対に定義するのが定番みたいなもんでしょうかね?
Re:コピーコンストラクタについて
Posted: 2008年10月13日(月) 01:20
by Justy
んー、絶対に必要というわけではないです。
クラスの内容によって変わります。
例えば、
[color=#d0d0ff" face="monospace]class A
{
public:
int a;
int b;
int c;
};[/color]
のように丸ごとコピーされても問題ない場合はどちらも必要有りません。
しかし、今回の stackクラスのような内部で何かをリソースを確保して、
デストラクタで解放するような構造だったりする場合は、
コピーコンストラクタや operator=()がないと、2つのクラスで同じリソースを
共有することになります。
そういうケースの場合、なんらかのリソースの共有機構(スマートポインタなど)を
持っていればいいのですが、そうでなければコピーコンストラクタや operator=()を
定義して、それを防ぐ必要があります。
余談ですが、定義ではなく宣言だけでもいい場合があります。
[color=#d0d0ff" face="monospace]
class stack
{
public:
stack(){}
...
private:
stack(const stack&); // 実装は不要
stack& operator=(const stack&); // 実装は不要
};[/color]
のように privateで宣言だけしておくとこのクラスは真っ当な手段では
コピーができなくなります。
コピーできないので同じリソースを共有することは無くなり、
安全といえば安全になりますね。
Re:コピーコンストラクタについて
Posted: 2008年10月13日(月) 01:24
by 大工
うんうん,なるほど.
詳しい解説ありがとうございました.