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

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

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

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

Q&A

解決済

5回答

4521閲覧

デストラクタが呼ばれない理由が知りたい

mushroom314

総合スコア29

C++

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

0グッド

0クリップ

投稿2018/03/07 05:13

newでインスタンス化するメソッドをポインタから呼び出すような以下のクラス:

c++

1#include <iostream> 2class Test{ 3 public: 4 bool allocated; 5 Test():allocated(true){ 6 std::cout << "constructer" << std::endl; 7 } 8 ~Test(){ 9 std::cout << "destructer" << std::endl; 10 if(allocated){ 11 delete this; 12 } 13 } 14 static Test* instantiate(){ 15 return (new Test); 16 } 17 void print(){ 18 std::cout << "hello world " << allocated << std::endl; 19 } 20};

を定義して、以下のmain関数を実行すると、

c++

1int main(void){ 2 Test* pt; 3 pt = pt->instantiate(); 4 pt->print(); 5 return 0; 6} 7

標準出力が

constructer

hello world 1

のみとなっていて、mainを抜けたところで「destructer」と表示されるのを期待していたのですが、そうなりませんでした。
ということは、このコードだとinstantiate()でnewされたメモリ領域がdeleteされていないことになるのでしょうか?

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

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

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

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

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

guest

回答5

0

こんにちは。

このコードだとinstantiate()でnewされたメモリ領域がdeleteされていないことになるのでしょうか?

その通りです。newで確保したら、deleteする必要があります。

ところで、pt = pt->instantiate();pt = Test::instantiate();の間違いではないでしょうか? 元のままの場合、ptを初期化しないまま使っているので不正メモリアクセスで落ちることがほとんどの筈です。
また、デストラクタ内の delete this; は禁忌事項です。deleteはデストラクタを呼び出しますから、この処理によりデストラクタが無限に呼ばれてしまいます。

最後に、こんな時はstd::unique_ptrを使うことがお薦めです。

C++

1#include <iostream> 2class Test{ 3 public: 4 bool allocated; 5 Test():allocated(true){ 6 std::cout << "constructer" << std::endl; 7 } 8 ~Test(){ 9 std::cout << "destructer" << std::endl; 10 if(allocated){ 11 // delete this; 12 } 13 } 14 static Test* instantiate(){ 15 return (new Test); 16 } 17 void print(){ 18 std::cout << "hello world " << allocated << std::endl; 19 } 20}; 21 22#include <memory> // std::unique_ptrを使えるようにする 23 24int main(void) 25{ 26 std::unique_ptr<Test> pt(Test::instantiate()); 27 pt->print(); 28 return 0; 29}

投稿2018/03/07 05:23

編集2018/03/07 05:31
Chironian

総合スコア23272

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

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

mushroom314

2018/03/07 06:49

丁寧なご説明ありがとうございます。今回は初期化していないポインタでもビルドできてしまったのですが、static関数の使い方を完全に間違えていました。deleteの無限ループも納得しました。スマートポインタの使用例も出していただきありがとうございます。
can110

2018/03/07 07:02

> ptを初期化しないまま使っているので不正メモリアクセスで落ちることがほとんどの筈です 気になったのでMSVCなどで試してみたところ「Test *p(nullptr); p = p->instantiate();」でもすんなり走ります。 staticメンバ呼出時、インスタンスに関する情報は不要なので 「p->static_func()」や「obj.static_func()」といったコード片は「Test::static_func()」としてコンパイル、といったことが規約なりで決まっているのかなあ?と思います。
guest

0

ベストアンサー

このコードだとinstantiate()でnewされたメモリ領域がdeleteされていないことになるのでしょうか?

その通りです。newしたものはdeleteで解放される際にデストラクタが走りますが、プログラム内で解放を行っていないし、mainにあるのはただのポインタで、スコープアウトしても何も起きないので、デストラクタに処理が移ることはありません。

投稿2018/03/07 05:23

maisumakun

総合スコア145183

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

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

mushroom314

2018/03/07 06:45

実体でなくポインタだとスコープアウトしても自動でデストラクタが呼ばれないことは知りませんでした。ありがとうございます。
guest

0

他の方が(当然のこととして?)述べておられない点があるような気がするので蛇足コメントしてみます。(C++の言語仕様は大変奥深い難しいものだと思うのでかなりおっかなびっくりコメントしています・・・)

ある型Tがあったとき、

T型のローカル変数の値はスコープを抜ける契機で破棄されますね。
T*型のローカル変数の値も同様に破棄されます。

ただし、T*型変数の値は「T型へのポインター値」であって「T型のインスタンス」ではない点に注意してください。

  • Tのライフサイクルを制御したい場合Tのコンストラクターとデストラクターでそれを定義できますが、制御対象はあくまでT型のインスタンスのみです。

  • T*型はT型とは異なる型ですので、Tの定義によってT*の振る舞いをどうこうすることはできません。

  • T*(つまりポインター値)の振る舞い(意味)は言語で固定的に「破棄する際には何も特別なことはせずに単に捨てる」と定められており、プログラマーがその意味を自由に変えられません。

それゆえ、T*の振る舞いを特別に定義したいならT*を(おそらくはメンバー変数として)包むような相応の型として定義する必要があります。ChironianさんやBeatStarさんが挙げておられるstd::unique_ptrなどの型はそういう目的の型ですが、これらの型は(前述したとおりの理由で)

unique_ptr ptr;として使うものであって
unique_ptr *ptr;のように使うことを意図したものではありません。


coffee time:
C++では引数がない関数の宣言をf()と書くのが自然だと思います。C言語では歴史的経緯のためf(void)と書かないと「引数がない」ことを表わせませんが、C++はそうではありません。C++でf(void)とも書けるのは単にCとの互換性のためだと思います。

投稿2018/03/07 06:45

KSwordOfHaste

総合スコア18394

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

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

mushroom314

2018/03/07 07:04

コメントありがとうございます。スコープを抜けるとポインタのアドレスは破棄されるが、ヒープに確保した領域はそのまま残るということですよね、理解が進みました。個人的にはmain(int argc, char** argv)との対比?でvoidと癖で書いてしまうのですが、確かに上に書いたコードでもf()とf(void)が統一されていないので、気をつけようと思います。
KSwordOfHaste

2018/03/07 07:13

> ポインタのアドレスは破棄されるが、ヒープに確保した領域はそのまま残ると はいそうです。それが本件のポイントのような気がしました。
guest

0

それはそうです。

C++

1Test test;

で生成した場合はint型とかみたいに自動的に破棄されますが、

C++

1Test test = new Test();

で生成したときはdelete をしないと破棄されません。

( 終了時にOSが破棄するっていうことを聞いたことがありますが、たぶんmain関数を抜け出た後だと思います。 でも人によって言っていることが違うので正確なことはわかりませんが。 )

また、

C++

1int main(void){ 2 Test* pt; 3 pt = pt->instantiate(); 4 pt->print(); 5 return 0; 6}

ってコンパイル通るんでしょうか? だって、クラス定義見ると instantiateメンバ関数を見るとstaticです。

本来なら

C++

1pt = Test::instantiate();

になるんじゃないでしょうか?

あと、allocatedメンバ変数もpublicになっている。

これではC言語で問題視されていたグローバル変数と同じです。

( アクセスするときにオブジェクトが付くかどうかの違いだけ。 )

メンバ変数はprivateにして、get/setを設けましょう。

で、話を戻しますが、メモリにはスタック領域ヒープ領域があるようです。

スタック領域っていうのは通常のint型とかみたいなデータを配置するところらしい。

で、ヒープ領域っていうのはC言語で言う malloc/freeで行うような動的データを確保する場所らしいです。

C++ならnew/delete ですね。

スタック領域なら自動的に開放されますが、ヒープ領域は自分で開放しないと無理らしい。

なのでdeleteは必須。

それが面倒なら C++11 あたりで導入された スマートポインタ ( shared_ptr, weak_ptr, unique_ptr ) を導入されてはいかがでしょうか?

( ただし、C++11としてコンパイルしないといけないはず。 )

C++スマートポインタ入門

投稿2018/03/07 05:25

編集2018/03/07 05:31
BeatStar

総合スコア4958

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

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

mushroom314

2018/03/07 06:53

丁寧なご説明ありがとうございます。スコープを外れるとデストラクタが呼ばれると勘違いしていたので、そこにdeleteを書いたのがそもそも違っていたのと、static関数の使い方も完全に間違えていました。よい勉強になったので、実装にはスマートポインタを使おうと思います。
guest

0

標準出力ってのはプログラムの開始時にOpenされて、終了時にCloseされます。
さて、そのデクストラクタが実行されるのは標準出力がCloseされる前なのか後なのかどっちでしょうか。

投稿2018/03/07 05:23

y_waiwai

総合スコア87747

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

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

mushroom314

2018/03/07 06:46

今回は問題点が別にあったようですが、タイミングの問題も頭の片隅に入れておきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問