質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

1383閲覧

コピーコンストラクタの仕組みを教えてください

iwanami

総合スコア9

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2020/05/31 13:01

以下のプログラムは参考書に書かれてあるプログラムです。

(あ)の、コピーコンストラクタの仕組みがイマイチ分かりません。
メモリの二重解放を防ぐために行われているという事は分かるのですが、ここの p はどのオブジェクトのpになるんでしょうか。
main()内でgetval()の仮引数に指定されているオブジェクトは、o になりますよね?
p が何をしているのかが分かりません。
どなたか宜しくお願いします。

// このプログラムにはエラーがある
####include <iostream>
####include <cstdlib>
using namespace std;

class myclass {
int *p;
public:
myclass(int i);
myclass(const myclass &ob);
~myclass() { delete p; }
friend int getval(myclass o);
};

myclass::myclass(int i)
{
p = new int;

if(!p) {
cout << "メモリ割り当てエラー\n";
exit(1);
}

*p = i;

}

myclass::myclass(const myclass &ob) ・・・(あ)
{
p = new(nothrow) int;

if(!p){ cout <<"メモリ割り当てエラー\n"; exit(1); } *p = *ob.p;

}

int getval(myclass o)
{
return *o.p; // 値を取得する
}

int main()
{
myclass a(1), b(2);

cout << getval(a) << " " << getval(b);
cout << "\n";
cout << getval(a) << " " << getval(b);
cout << "\n";

return 0;
}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2020/05/31 20:36 編集

本来は *a.p と *b.p が別の値を持てるようにするためですが、メモリの二重解放を防ぐ意味もあります。 よく見たら getval の引数はコピー渡しされているので、コピーコンストラクタがないとメモリの二重解放になりますね。
iwanami

2020/06/01 05:06

僕が参考書で学んだのはメモリの二重解放だけだったので、*a.pと*b.pが別の値を持てるようにするため、というのは知りませんでした。 ただ今回はメモリの二重解放に焦点を当てていて、(あ)のコピーコンストラクタの定義でなぜ二重解放が防げているのかが分かりません。
iwanami

2020/06/01 07:10

回答してくれた皆さんありがとうございました。
guest

回答1

0

ベストアンサー

二重解放とならぬよう...

C++

1myclass::myclass(const myclass &ob) { // ・・・(あ) 2 p = new(nothrow) int; // 新たな領域を確保し 3 if(!p){ 4 cout <<"メモリ割り当てエラー\n"; 5 exit(1); 6 } 7 *p = *ob.p; // ナカミを複製する 8}

なので
myclass a(123);
myclass b = a;
としたとき、a.p と b.p は別領域となります(からa,bが廃棄されるとき二重解放になりません)

[追記] 領域のnew/delete の様子をプリントしてみました:

C++

1#include <iostream> 2#include <cstdlib> 3using namespace std; 4 5class myclass { 6 int* p; 7public: 8 myclass(int i); 9 myclass(const myclass& ob); 10 ~myclass() { std::cout << "delete " << (void*)p << "\n"; delete p; } 11 friend int getval(myclass o); 12}; 13myclass::myclass(int i) 14{ 15 p = new int; 16 std::cout << "new " << (void*)p << "\n"; 17 if (!p) { 18 cout << "メモリ割り当てエラー\n"; 19 exit(1); 20 } 21 *p = i; 22} 23myclass::myclass(const myclass& ob) 24{ 25 p = new(nothrow) int; 26 if (!p) { 27 cout << "メモリ割り当てエラー\n"; 28 exit(1); 29 } 30 std::cout << "new " << (void*)p << "\n"; 31 *p = *ob.p; 32} 33int getval(myclass o) 34{ 35std::cout << "getval\n"; 36 return *o.p; // 値を取得する 37} 38int main() 39{ 40 myclass a(1), b(2); 41 cout << getval(a) << " " << getval(b); 42 cout << "\n"; 43 cout << getval(a) << " " << getval(b); 44 cout << "\n"; 45 return 0; 46}

実行結果:

new 000002324AA76DE0 new 000002324AA76D40 new 000002324AA76E80 getval delete 000002324AA76E80 1 new 000002324AA76E10 getval delete 000002324AA76E10 2 new 000002324AA76DD0 getval delete 000002324AA76DD0 1 new 000002324AA76CF0 getval delete 000002324AA76CF0 2 delete 000002324AA76D40 delete 000002324AA76DE0

getvalの前後でnew/deleteされてて、そのポインタ値は組になってます。
つまり、getvalの直前でコピーを作り、直後でそのコピーを廃棄しています。
なので二重解放になりません。

投稿2020/05/31 13:11

編集2020/06/01 06:19
episteme

総合スコア16614

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

iwanami

2020/05/31 13:51 編集

pが代わりに解放されるから o は解法されないということでしょうか?
episteme

2020/05/31 14:17

ごめん何言ってんのかわかんない。 a.p と b.p は別物なので、aが廃棄されても b.p に影響ありません。(逆もまた)
iwanami

2020/06/01 04:54

もうちょっと他に言い方ないですかね a.p と b.p が別物だという事は認識してます。 時間差で申し訳ないんですが、hoshi-takanori さんも言っているように本来は 「*a.p と *b.p が別の値を持てるようにする 」意味がコピーコンストラクタにあるようなのですが、今回はそれではなくメモリの二重解放を防ぐために焦点を当てています。 というか参考書ではそっちしか説明されてなかったので。 epistemeさんが言っている、a=b とした時にメモリは共有されず、a,bのpの解法時に二重解放にならないというのは、なるほどとは思ったのですが、それとは多分僕が言っている事は違うと思います。 伝わればいいんですが (あ)のコピーコンストラクタを消した状態で実行すると、 1 2 7475856 7475640 と出力されるので、コピーコンストラクタを設定してない状態でgetval()を使うと、a,bのそれぞれのpのメモリが解放されて、次のgetval()を使う時にめちゃくちゃな値が出力されてしまいます。 そのメモリの解放を防ぐやり方が(あ)なのでしょうが、(あ)でやってる事は pにo.pの値を代入しているだけなので、getval()が終わった後に、結局pが解放されると思うのですが... このように認識していて、この p が o.p の代わりに解放されるから、o.p は解法されないのかなと思っているのですが (あ)で p に新たな領域を確保して、o.p のナカミを複製するというのは分かるのですが、それだけでメモリの二重解放が防げている理由がわかりません。
episteme

2020/06/01 05:07 編集

>もうちょっと他に言い方ないですかね いやホントわかんない。 「pが代わりに解放されるから o は解法されないということでしょうか? 」 ... oってなんですか? myclassのメンバはpだけですが。 ----------------------------- 二重解放を避けたいなら、回答が示すとおり 1. 複製を作る さもなくば 2. そもそもコピーを許さない のいずれかです。 コピーを許し、かつ二重解放を防ぎたい となれば 3. その領域を最後に手放すインスタンスが解放する からくりが必要となります。標準C++では std::shared_ptr がそのからくりに相当します。
iwanami

2020/06/01 05:17

あ、すいません。oじゃなかったです。ob.pでした(^^; 訂正しておきます。 それを踏まえた上で僕の説明では分かりませんか
episteme

2020/06/01 05:25

> (あ)でやってる事は pにo.pの値を代入しているだけなので、getval()が終わった後に、結局pが解放されると思うのですが... myclass::myclass(const myclass &ob) ・・・(あ) {  p = new(nothrow) int;  if(!p){ cout <<"メモリ割り当てエラー\n"; exit(1); } *p = *ob.p; // ココ!! } p = ob.p ではありませんよ、*p = *ob.p ですから p はこの関数で確保された領域のままです。確保のあと書き換えられていません。 # 書き換えられたのは pの指す先。
iwanami

2020/06/01 05:58

それもなるほどとは思うのですが、、僕が言っているのとは多分違うんだと思います。 getval(a)は、aを値引数で指定しているので、コピーが生成されて、getval(a)が終了する時にデストラクタが呼び出されて、a.pが解放されてしまいます。 コピーコンストラクタの定義によってそれを防ぐ事が出来るようですが、この定義の内容でどうやってメモリの解放を防いでいるのかが分かりません。
yuki23

2020/06/01 06:04

episteme さんは正にその説明をされてますよ。回答をちゃんと読みましょう
episteme

2020/06/01 06:20

試運転してみた(追記しました)
SHOMI

2020/06/01 06:24

>getval(a)が終了する時にデストラクタが呼び出されて、a.pが解放されてしまいます。 デストラクタが呼ばれるのはgetval(myclass o)を呼ぶ際に作られたoのほうで、解放されるのはo.pですよ。
iwanami

2020/06/01 06:27

すいません、自分の理解が足りないため長引かせてしまっているんですね。 epistemeさんの回答をもう一度読んで解釈したのは getval(a)で値引数に a を指定すると、コピーコンストラクタの定義のおかげで、aのコピー(メモリは共有しない)が int getval(myclass o){} の o に引数として指定され、それが代わりに解放されるという事でしょうか。
episteme

2020/06/01 08:20 編集

> int getval(myclass o){} の o に引数として指定され、それが代わりに解放されるという事でしょうか。 その様子を観察しているのが[追記]です。
iwanami

2020/06/01 06:32

度々すいません、回答に気づかなかったので上のコメントは15:04までの返信です。今から理解したいと思います。
iwanami

2020/06/01 07:05 編集

(一つ上の回答も15:27までの返信です。返信遅くてすいません。) なるほどー!! 今漸く分かりました。 理解出来ない事が多くて少し逆切れ気味だった所もあり(逆切れしているつもりはないんですが、そう見えるコメントは合ったかと思います)、僕の理解が足りないために長く付き合わせてしまってすいません。 epistemeさんのプログラムのおかげで流れを目で追って理解する事ができました。 ありがとうございます。
iwanami

2020/06/01 10:36 編集

まだまだ自分が初心者だと分かりました。 episteme さんには分かりやすいプログラムまで記述してくださり感謝の気持ちで一杯です。 回答してくれた皆さんありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問