• 締切済み

◆コピコンを実装しないと、returnでエラー?

ポインタを持ったクラスで、 コピーコンストラクタを実装していないと、 newを使用した際にエラーになるそうですが、 なぜなのでしょうか? 値渡しの関数などで、 コピーコンストラクタを実装していないと、 デフォルトコピーコンストラクタで、ポインタ型のメンバ変数の値もコピーするため、 return する前に、 「一時的な変数としてコピーされていた値渡しの引数」が、 関数の終了時に解放され、 「アドレスを指しているポインタ」が指し示す先の領域に「delete」が走ってしまうため、 呼び元の変数で持っているポインタが指し示す先の領域も、 解放されてしまうとかでしょうか?? そもそも、 ポインタ pMem=NULL は、アドレスの指し示す値のリセットで、 Delete pMem は、アドレスが指し示す先頭から、そのクラスで必要としている分のメモリ量進んだアドレスまでを、解放する。 ということで合っているでしょうか? それとも、 「ポインタ」を削除しても、ポインタの指し示す先のアドレス自体は存在していて、 「呼び元」のアドレス格納用領域と、 「関数の呼び出し時にコピーコンストラクタで作られるアドレス格納用領域」は別であり、 ポインタ自体を削除しても、ポインタの先にあるメモリ領域は残っているのでしょうか?

みんなの回答

  • weavaest
  • ベストアンサー率15% (157/1020)
回答No.3

関数の引数にインスタンスの実態を渡した場合には、コピーコンストラクタが実行されます。ですから、関数から抜ける際にはデストラクタが実行されます。 コピーコンストラクタで、コピー元のポインター値がそのままコピー先のポインター値に書き写されるなら、コピー元、コピー先の2つのインスタンスのポインター変数は同じ領域を指すことになりますよね。 デストラクタに、ポインター変数の指す領域を開放する処理があるなら、そこでコピー元のインスタンスが持つポインターが開放されてしまいますね。 ポインター変数にNULLを設定した場合に起きることは、ポインター値がNULLになるだけです。確保された領域が開放されるわけではありません。 deleteはnewで確保された領域を開放するものです。

TeferiMage
質問者

お礼

お返事遅れてしまいすみません! ありがとうございました!

回答No.2

> ポインタを持ったクラスで、 > コピーコンストラクタを実装していないと、 > newを使用した際にエラーになる という状況はこれで作れていますでしょうか? class A { int* i; }; int main(void) { A* a = new A; delete a; return 0; } FreeBSD 10.1上のclang 3.4.1でコンパイルしてみましたが、A::iを使っていないという警告くらいしか見ませんでした。違う場合やエラーにする処理系がある場合、補足してください。 ポインタ変数にNULLを代入するという操作はポインタ変数の値をNULLにしただけで、その指し先が消えるわけではありません。newで確保したものの場合、誰かが明示的にdeleteをしないと確保されたメモリーは解放されません。確保されたメモリーが解放されないと、誰も使っていない領域でメモリーが埋め尽くされ、最終的にメモリーが足りなくなるメモリーリークという不具合に悩まされることになります。 C++だとコーディング規約が許すなら、STL (標準テンプレートライブラリー)を使い、自分でポインタの管理をするのを最小限にします。たとえば、スマートポインターを使うと、たとえば関数の終わりなどスコープを抜けた時点でポインタ変数の指す先は自動的にdeleteされます。あとはvectorの使い方にも慣れるとよいでしょう。 > ポインタ自体を削除しても、ポインタの先にあるメモリ領域は残っているのでしょうか? ポインタ自体というのはポインタを含むオブジェクトを削除しても、ポインターの指し先にあるメモリーが残るかと言うことですよね? はい。一つのオブジェクトを複数のクラスで共有して使用することは普通にあるので、それができないと困ります。この場合も含め、ポインターを使ったプログラムを書く場合、誰がそのポインターの指し先の所有者か(言い換えると、不要になったときに消す義務を持つか)を明確にしないとメモリーリークの温床になります。 参考までに、こんなコードはどうでしょう。 #include <iostream> class A { public: // don't take ownership of i. void SetI(int* i) { i_ = i; } void PrintI() { std::cout << *i_ << std::endl; } private: int* i_; }; int main(void) { A* a0 = new A; A* a1 = new A; int* i = new int; *i = 100; a0->SetI(i); a1->SetI(i); a0->PrintI(); delete a0; // 質問者の考えだとここでiもdeleteされてしまうが、 // 本当はdeleteされないので、a1のi_の指し先はまだ生きている。 a1->PrintI(); delete a1; // ここでiの指し先を削除。 delete i; return 0; } ちなみに、スマートポインターを使うとこのコードがこうなります。 #include <iostream> #include <memory> class A { public: // don't take ownership of i. void SetI(int* i) { i_ = i; } void PrintI() { std::cout << *i_ << std::endl; } private: int* i_; }; int main(void) { std::unique_ptr<A> a0(new A); std::unique_ptr<A> a1(new A); std::unique_ptr<int> i(new int); *i = 100; a0->SetI(i.get()); a1->SetI(i.get()); a0->PrintI(); a0.reset(); // 明示的にa0をdelete a1->PrintI(); a1.reset(); // 明示的にa1をdelete return 0; // iはスコープを抜けたところで自動的にdelete } iをうっかりdeleteし忘れることもありません。

TeferiMage
質問者

お礼

お返事遅れてしまいすみません! ありがとうございました!

  • maiko0318
  • ベストアンサー率21% (1483/6970)
回答No.1

Cの世界では確保したメモリーは開放するまで残っています。 大きなプログラムですと、きちんと開放しないとメモリーが足りなくなって 動かなくなります。 OSによってはプログラム終了でメモリーの解放を行わないものもあるので コンピュータの再立ち上げをするはめに陥ります。 ポインタ pMem=NULL は、アドレスの指し示す値のリセット=○ Delete pMem は、アドレスが指し示す先頭から、そのクラスで必要としている分のメモリ量進んだアドレスまでを、解放する。=○ 「ポインタ」を削除しても、ポインタの指し示す先のアドレス自体は存在していて=× 「関数の呼び出し時にコピーコンストラクタで作られるアドレス格納用領域」は別であり、 ポインタ自体を削除しても、ポインタの先にあるメモリ領域は残っているのでしょうか? =関数を呼び出すとき、ポインターで渡していれば(アドレスを渡しているので) 関数側でポインターを削除すれば戻ってもポインターは使えなくなります。 「値渡しの関数」ということですが、int や charは値渡しで関数を呼び出した時に 違うアドレスにコピーされていますが、ポインターを渡したら関数側で同じアドレスを指します。 値を変更したらメイン側へ戻っても値は変わってしまいます。

TeferiMage
質問者

お礼

お返事遅れてしまいすみません! ありがとうございました!

関連するQ&A

  • C++のnewで確保した領域について

    こんにちわ。C++を勉強し始めた者です。 new演算子を使ってインスタンスを生成した場合、それはスタックではなくヒープ領域に確保され、不要になったらdeleteを使って領域を解放しなければいけない認識です。 C++の初心者向けサンプルコードを見ていて疑問があったので質問させてください。 (例)クラスA.cpp ======================== #include <Car> #include <Garage> ~略~ クラスAのコンストラクタ{ Car *mycar = new Car("プリウス"); addGarage( mycar ); } クラスAのデストラクタ{ } ======================== 上記のような実装のクラスAがあったのですが、コンストラクタでCarクラスのインスタンス生成をして、オート変数の*mycarに格納して、Garageの公開関数に渡しています。 質問1:このクラスAをインスタンス生成した場合、コンストラクタで確保したヒープ領域は、プログラム終了時まで解放されない認識であっていますか? 質問2:オート変数の*mycarはコンストラクタからreturnした時点で解放されてしまうので、今のままではデストラクタでヒープ領域をdeleteできない認識であっていますか? 質問3:newで生成したインスタンスへのポインタは、その関数内でdeleteしない場合、メンバ変数やstatic変数、グローバル変数に格納しなければdeleteできなくなるという理解であっていますか? 質問4:C++のコードでnewした戻り値をオート変数に格納するプログラムは通常使うことはあるのですか?

  • C++でUNDOを実装しようとしています。

    C++でUNDOを実装しようとしています。 処理前にクラスオブジェクトをコピーしておき、 UNDOの処理では処理前のクラスを元のクラスに かぶせてしまえば処理前の状態に戻ると思うの です。 ところが、クラスオブジェクトの中でポインタ 変数があり、コピーした内容も書き換わってし まいます。 いわゆるディープコピーをしなければならない らしいということまで分かったのですが、その 方法がわかりません。 サンプルなどが掲載されているホームページが ありましたら、そのURLを教えてください。

  • 参照型を格納できるコンテナについてなど

    ちょっと長文です。下のほうに質問があります。 STLのvectorコンテナを使用しているのですが、 困った事態が発生しています。 自分は、参照型を要素として、持たしたいのですが、 持たすことができません。 なにやら調べてみたところ、STLのコンテナクラスというのは 基本的に「値ベースのコンテナ」らしく、「参照ベースのコンテナ?」 としてコンテナを使うには、ポインタ型を格納して下さい。との ことでした。 ただし、この方法は2重deleteが発生する危険性を孕んでいるので、 Boostのなんかのポインタークラス?のようなものを使えば、 そのような問題に悩まされることないですよー。とありました。 ここで問題なのは、 ・Boostを扱えるだけの知識がない。 ・そもそもBoostを使えるまで環境設定できる自信がない。 ということです。 そのため、普通のポインタを使って実装しようと思うのですが、 そして上記のような問題が出てくるにつれ自分の中では 次のような疑問点が出てきました。 ●質問(1) ・なんで参照型を格納できるコンテナがないのよ!  本当はあったりするんだけども、自分が知らないだけ? ●質問(2) ・関数間でオブジェクトを渡すときには、パフォーマンスとかも  考慮してもconstキーワードを使いつつの参照渡しがよい。と  Effective C++か何かで読んだのですが、コンテナに格納する  場合にはこれは有効ではないのか?  また、オブジェクトは、基本的には、やたらめったら  コピーするものではなく、一つオブジェクト用のメモリ領域を  作ったらそれを流用(ポインタ・参照を使って参照する)した方が、  作り的にきれいな気がするのですが、なにか方針とかあったりする  のでしょうか? 以上長くなってしまいましたが、よろしくお願いいたします。

  • 変数

    ポインタ変数なんですが、 int *p;と宣言した時 自分自身のアドレスを持ってますか?(プログラムが終わるまで?) ポインタ変数は、他の変数アドレスを格納しますよね。 また数値の値も格納しますよね。 これは、ポインタ変数宣言 時の自分のアドレスに、 他の変数のアドレス、数値の値、 と二つを格納しているのですか? ポインタ自身のアドレス出力は(int *pの時) printf("%p",&p); 他の変数のアドレス出力は(p=&の時) printf("%p",p); であってますか? 普通の変数は、自身のアドレス、数値の値、だけ、 ポインタ変数はさらに、他の変数の値を格納していますか? 教えてください。

  • フレームポインタについて

    こんにちは x86の場合、フレームポインタは関数呼び出し直後のスタックポインタの値を保持してると書いてありました スタックは引数、リターンアドレス、ebp保存値、ローカル変数の順番で格納されると認識しています 関数呼び出し直後のスタックポインタの値とは、正確にはどこを指してるのでしょうか よろしくお願いします

  • ポインタの宣言

    ポインタを宣言するとメモリ上に、ポインタ変数を格納するための領域が確保されます。ポインタ=アドレスというのは大丈夫なのですが、 int *b のようにどうして、ポインタに型があるのでしょうか?単に変数のアドレスを表示するだけならば型はいらないと思うのですが。 またこのとき宣言された変数は *b ではなくて b であってますよね?

  • 基本情報平成4年春の問題

    基本情報平成4年春の問題で問題集の答えが、納得いきません。どなたかよったら 教えてください。 問題 リストヘの登録 次の流れ図中の(a)~(C)に適切な字句を補い,流れ図を完成せよ。 〔流れ図の説明〕 流れ図は,ファイルから数値データを読み込み,配列に格納するとともに,昇順にポイ ンタでつなぐ処理を表す。 (1)配列の要素は,データ領域(DATA)とポインタ領域(POlNTER)からなる。データ領域に は,数値データを読み込んだ順に格納する。ポインタ領域には,各数値データを昇順に 並べた場合,その数値データの直後にくるデータが格納されている配列要素番号を格納 する。1件の数値データをデータ領域に格納するたびに,対応するボインタ領域に値を 格納する。このとき,必要があればそれまでにセットしたポインタ領域の値を修正する。 (2)一番大きい値のデータが格納されている配 列要素のポインタ領域の値は,0にする。 一番小さい値のデータが格納されている配 列要素の要素番号を,変数MlNに格納する。 配列にデータが1件も格納されてし/ない場 合,変数MlNの値は0である。 (3)同じ値のデータが配列中に存在する場合、 先に格納されたデータほど小さい値とみなして処理を行う

  • templateクラスについて

    先ほど以下のようなプログラムを書いたのですがコンパイルを通すことができません。 //適当なポインタを保持するだけのクラス template <class _type> class hoge { private:   //適当に変数を保持   _type val; public:   //コンストラクタで適当に値をセット   hoge() : val( 0 ){}   //このクラスから唯一ポインタを引っ張ってくる方法   friend _type getVal( const hoge& foo )   {     // そのまま返す     return foo.val;   } }; void func( const hoge<int>& foo ) {   //値を引き出す   getVal( foo ); } void main() {   //実体化   hoge<int> foo;   //値を引き出す   getVal( foo );   //関数の先で値を引き出す   func( foo ); } 上記のようなプログラムを書いたのですが、main関数内でgetValを呼び出す場合はとくに問題ないのですがfunc関数を呼び出してfunc関数内でgetValを呼び出すと error C3861: 'getVal': 識別子が見つかりませんでした error C2365: 'getVal' : 再定義; 以前の定義は '以前は不明な識別子' でした。 コンパイルされたクラスの テンプレート のインスタンス化 'hoge<_type>' の参照を確認してください というエラーが出てしまいます。 func関数の引数を( const hoge<int>& foo )からvoid func( hoge<int> foo )のように参照渡しから実体渡しに変更するとコンパイルが通り、実行もできるのですが、なぜこれでコンパイルが通るのか理由がいまいちよくわかりません。 またやはり、コンストラクタ、デストラクタの問題などから実体渡しより、参照渡しを使いたいのですがどのようにプログラムを書けば今回の問題を解決できますでしょうか。 よろしくおねがいします。 /* VisualStudio2005 AcademicEdition MicroSoft WindowsXP Professional 32bit */

  • 関数への変数の受け渡しについて

    関数に変数を受け渡すとき、配列とそれ以外の変数では受け渡すものが違いますよね。 変数の場合は、変数の値を関数側の変数にコピーする。 配列の場合は、配列そのもの(配列のポインタ)を関数に渡す。 なぜ配列の場合は値のコピーではなくて、ポインタを渡す仕様になっているんでしょうか。 ひとつめは、どのような意図でそのような仕様になっているのかという質問です。 もうひとつは、関数に配列の値だけを渡すにはどうすればいいのか、つまり元の配列のほうの値は書き換えないで欲しいというときはどうすればよいのかという質問です。 よろしくおねがいします。

  • 指定したメモリアドレスの値の読み出し(自己改変コードの作成)

    C言語で、自己改変コードの作成を行っています。 実行時にmain関数の中で、そのコードが含む(main関数ではない)別の関数がロードされているメモリ領域に対して処理を行い、自己改変を実現しようと考えています。 変更を加えたい関数がロードされたメモリ領域を特定し、そのメモリ領域への書き込みをmprotectシステムコールを用いて書き込み可能にするところまではできました。 しかし、その先、そのメモリ領域の内容を読み出し、変更を加えるところがどうもうまくできません。 例えば、あるメモリアドレス0x804845cに格納されている値を読み出したい場合、どのようにしたら良いのでしょうか? さらに、そこに格納されている値を変更したい場合、memsetシステムコールを用いて書き込みを行おうと考えているのですが、正しいでしょうか? (例:メモリアドレス0x804845cに値0x23を格納したい場合 memset((0x804845cへのポインタ), 0x23, 1); のようにする) アドバイスをお願いいたします。

専門家に質問してみよう