デストラクタが呼ばれない理由が知りたい
解決済
回答 5
投稿
- 評価
- クリップ 0
- VIEW 10K+
newでインスタンス化するメソッドをポインタから呼び出すような以下のクラス:
#include <iostream>
class Test{
public:
bool allocated;
Test():allocated(true){
std::cout << "constructer" << std::endl;
}
~Test(){
std::cout << "destructer" << std::endl;
if(allocated){
delete this;
}
}
static Test* instantiate(){
return (new Test);
}
void print(){
std::cout << "hello world " << allocated << std::endl;
}
};
を定義して、以下のmain関数を実行すると、
int main(void){
Test* pt;
pt = pt->instantiate();
pt->print();
return 0;
}
標準出力が
constructer
hello world 1
のみとなっていて、mainを抜けたところで「destructer」と表示されるのを期待していたのですが、そうなりませんでした。
ということは、このコードだとinstantiate()でnewされたメモリ領域がdeleteされていないことになるのでしょうか?
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+2
このコードだとinstantiate()でnewされたメモリ領域がdeleteされていないことになるのでしょうか?
その通りです。new
したものはdelete
で解放される際にデストラクタが走りますが、プログラム内で解放を行っていないし、main
にあるのはただのポインタで、スコープアウトしても何も起きないので、デストラクタに処理が移ることはありません。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+2
こんにちは。
このコードだとinstantiate()でnewされたメモリ領域がdeleteされていないことになるのでしょうか?
その通りです。newで確保したら、deleteする必要があります。
ところで、pt = pt->instantiate();
はpt = Test::instantiate();
の間違いではないでしょうか? 元のままの場合、ptを初期化しないまま使っているので不正メモリアクセスで落ちることがほとんどの筈です。
また、デストラクタ内の delete this;
は禁忌事項です。deleteはデストラクタを呼び出しますから、この処理によりデストラクタが無限に呼ばれてしまいます。
最後に、こんな時はstd::unique_ptrを使うことがお薦めです。
#include <iostream>
class Test{
public:
bool allocated;
Test():allocated(true){
std::cout << "constructer" << std::endl;
}
~Test(){
std::cout << "destructer" << std::endl;
if(allocated){
// delete this;
}
}
static Test* instantiate(){
return (new Test);
}
void print(){
std::cout << "hello world " << allocated << std::endl;
}
};
#include <memory> // std::unique_ptrを使えるようにする
int main(void)
{
std::unique_ptr<Test> pt(Test::instantiate());
pt->print();
return 0;
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
それはそうです。
Test test;
で生成した場合はint型とかみたいに自動的に破棄されますが、
Test test = new Test();
で生成したときはdelete をしないと破棄されません。
( 終了時にOSが破棄するっていうことを聞いたことがありますが、たぶんmain関数を抜け出た後だと思います。 でも人によって言っていることが違うので正確なことはわかりませんが。 )
また、
int main(void){
Test* pt;
pt = pt->instantiate();
pt->print();
return 0;
}
ってコンパイル通るんでしょうか? だって、クラス定義見ると instantiateメンバ関数を見るとstaticです。
本来なら
pt = 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としてコンパイルしないといけないはず。 )
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
他の方が(当然のこととして?)述べておられない点があるような気がするので蛇足コメントしてみます。(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との互換性のためだと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
-1
標準出力ってのはプログラムの開始時にOpenされて、終了時にCloseされます。
さて、そのデクストラクタが実行されるのは標準出力がCloseされる前なのか後なのかどっちでしょうか。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.20%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2018/03/07 15:45