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

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

ただいまの
回答率

87.49%

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

解決済

回答 5

投稿

  • 評価
  • クリップ 0
  • VIEW 12K+

score 29

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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

checkベストアンサー

+2

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/07 15:45

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

    キャンセル

+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;
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/07 15:49

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

    キャンセル

  • 2018/03/07 16:02

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

    キャンセル

+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としてコンパイルしないといけないはず。 )

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/07 15:53

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

    キャンセル

+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との互換性のためだと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/07 16:04

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

    キャンセル

  • 2018/03/07 16:13

    > ポインタのアドレスは破棄されるが、ヒープに確保した領域はそのまま残ると

    はいそうです。それが本件のポイントのような気がしました。

    キャンセル

-1

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/07 15:46

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

    キャンセル

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

  • ただいまの回答率 87.49%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る