web-dev-qa-db-ja.com

戻り値を持つC ++デストラクタ

C++で、クラスデストラクターを次のように定義する場合:

~Foo(){
   return;
}

このデストラクタを呼び出すと、Fooのオブジェクトが破棄されるか、デストラクタから明示的に返されるということは、それを破棄したくないことを意味します。

特定のオブジェクトが別のオブジェクトデストラクターを介してのみ、つまり他のオブジェクトを破棄する準備ができたときにのみ破棄されるようにします。

例:

class Class1{
...
Class2* myClass2;
...
};

Class1::~Class1(){
    myClass2->status = FINISHED;
    delete myClass2;
}

Class2::~Class2(){
    if (status != FINISHED) return;
}

私はオンラインで検索したが、私の質問に対する答えを見つけることができなかった。また、デバッガーを使用していくつかのコードをステップバイステップで実行することで自分で理解しようとしましたが、最終的な結果を得ることができません。

39
Rasula

いいえ、returnステートメントによってオブジェクトが破棄されるのを防ぐことはできません。dtorの本体の実行がその時点で終了することを意味します。その後も(メンバーとベースを含む)破棄され、メモリの割り当ては解除されます。

例外をスローします。

Class2::~Class2() noexcept(false) {
    if (status != FINISHED) throw some_exception();
}

Class1::~Class1() {
    myClass2->status = FINISHED;
    try {
        delete myClass2;
    } catch (some_exception& e) {
        // what should we do now?
    }
}

それは確かにひどいアイデアであることに注意してください。あなたはデザインを再考する方が良いでしょう、私はより良いものがあるに違いないと確信しています。

[〜#〜] edit [〜#〜]

私は間違いを犯しました。例外を投げてもそのベースとメンバーの破壊は止まらず、Class2のdtorのプロセス結果を取得することができます。そして、それで何ができるかはまだ明らかではありません。

45
songyuanyao
~Foo(){
   return;
}

次とまったく同じ意味です:

~Foo() {}

void関数に似ています。 return;ステートメントなしで最後に到達することは、最後にreturn;を持つことと同じです。

デストラクタには、Fooの破棄プロセスが既に開始されているときに実行されるアクションが含まれています。プログラム全体を中断せずに破壊プロセスを中断することはできません。

24
M.M

[D] oデストラクタから明示的に戻るということは、デストラクタを破壊したくないということですか

いいえ。早期復帰(_return;_または_throw ..._による)は、デストラクタの本体の残りが実行されないことを意味します。ベースとメンバーはまだ破棄され、デストラクタはまだ実行されています。 [except.ctor]/ を参照してください。

初期化または破棄が例外によって終了するストレージ期間のクラスタイプのオブジェクトの場合、オブジェクトの完全に構築されたサブオブジェクトごとにデストラクタが呼び出されます...

この動作のコードサンプルについては、以下を参照してください。


特定のオブジェクトが別のオブジェクトデストラクターを介してのみ、つまり他のオブジェクトを破棄する準備ができたときにのみ破棄されるようにしたいのです。

質問は所有権の問題に根ざしているようです。親が非常に一般的なイディオムで破棄され、1つ(ただしこれに限定されない)で達成されると、「所有」オブジェクトを削除します。

  • 構成、それは自動メンバー変数です(つまり、「スタックベース」)
  • 動的オブジェクトの排他的所有権を表す_std::unique_ptr<>_
  • 動的オブジェクトの共有所有権を表す_std::shared_ptr<>_

OPのコード例では、_std::unique_ptr<>_が適切な代替手段になる可能性があります。

_class Class1 {
  // ...
  std::unique_ptr<Class2> myClass2;
  // ...
};

Class1::~Class1() {
    myClass2->status = FINISHED;
    // do not delete, the deleter will run automatically
    // delete myClass2;
}

Class2::~Class2() {
    //if (status != FINISHED)
    //  return;
    // We are finished, we are being deleted.
}
_

サンプルコードのif条件チェックに注意してください。状態が所有権と寿命に結びついていることを示唆しています。それらはすべて同じものではありません。確かに、特定の状態に達したオブジェクトをその「論理的」存続期間に結び付けることができます(つまり、クリーンアップコードを実行します)が、オブジェクトの所有権への直接リンクは避けます。ここに含まれるセマンティクスのいくつかを再考するか、オブジェクトの開始状態と終了状態を「自然な」構築と破壊に指示することをお勧めします。

サイドノート;デストラクタで何らかの状態を確認する必要がある場合(または何らかの終了条件をアサートする場合)、throwの代わりに、その条件が満たされない場合に_std::terminate_を(何らかのログを記録して)呼び出すことができます。このアプローチは、既にスローされた例外の結果としてスタックをアンワインドするときに例外がスローされるときの標準の動作と結果に似ています。これは、_std::thread_が未処理の例外で終了するときの標準的な動作でもあります。


[D] oデストラクタから明示的に戻るということは、デストラクタを破壊したくないということですか

いいえ(上記を参照)。次のコードはこの動作を示しています。 ここにリンク および 動的バージョンnoexcept(false)は、std::terminate()が呼び出されないようにするために必要です

_#include <iostream>
using namespace std;
struct NoisyBase {
    NoisyBase() { cout << __func__ << endl; }
    ~NoisyBase() { cout << __func__ << endl; }
    NoisyBase(NoisyBase const&) { cout << __func__ << endl; }
    NoisyBase& operator=(NoisyBase const&) { cout << __func__ << endl; return *this; }    
};
struct NoisyMember {
    NoisyMember() { cout << __func__ << endl; }
    ~NoisyMember() { cout << __func__ << endl; }
    NoisyMember(NoisyMember const&) { cout << __func__ << endl; }
    NoisyMember& operator=(NoisyMember const&) { cout << __func__ << endl; return *this; }    
};
struct Thrower : NoisyBase {
    Thrower() { cout << __func__ << std::endl; }
    ~Thrower () noexcept(false) {
        cout << "before throw" << endl;
        throw 42;
        cout << "after throw" << endl;
    }
    NoisyMember m_;
};
struct Returner : NoisyBase {
    Returner() { cout << __func__ << std::endl; }
    ~Returner () noexcept(false) {
        cout << "before return" << endl;
        return;
        cout << "after return" << endl;
    }
    NoisyMember m_;
};
int main()
{
    try {
        Thrower t;
    }
    catch (int& e) {
        cout << "catch... " << e << endl;
    }
    {
        Returner r;
    }
}
_

次の出力があります。

_NoisyBase
NoisyMember
Thrower
before throw
~NoisyMember
~NoisyBase
catch... 42
NoisyBase
NoisyMember
Returner
before return
~NoisyMember
~NoisyBase
_
17
Niall

C++標準(12.4デストラクタ)によると

8デストラクタの本体を実行し、本体内に割り当てられた自動オブジェクトを破棄した後、クラスXのデストラクタは、Xの直接非バリアント非静的データメンバのデストラクタ、Xの直接基本クラスのデストラクタを呼び出し、Xが最も派生したクラス(12.6.2)のタイプ、そのデストラクタはXの仮想ベースクラスのデストラクタを呼び出します。すべてのデストラクタは、修飾名で参照されているかのように呼び出されます。つまり、派生クラスで仮想オーバーライド可能なデストラクタを無視します。ベースとメンバーは、コンストラクターの完了と逆の順序で破棄されます(12.6.2を参照)。 デストラクタ内のreturnステートメント(6.6.3)は、呼び出し元に直接戻らない場合があります。制御を呼び出し元に転送する前に、メンバーとベースのデストラクタが呼び出されます。配列の要素のデストラクタは逆に呼び出されます。構築の順序(12.6を参照)。

したがって、返されるステートメントは、デストラクタが呼び出されるオブジェクトが破棄されることを妨げません。

8

デストラクタから明示的に戻ることは、デストラクタを破棄したくないことを意味します。

番号。

デストラクタは関数なので、その内部でreturnキーワードを使用できますが、オブジェクトの破壊を防ぐことはできません。デストラクタ内にいると、すでにオブジェクトを破壊しているため、前に発生しなければならないことを防ぐ。

何らかの理由で、デザイン問題はshared_ptrおよび多分カスタム削除機能がありますが、その場合は上記の問題に関する詳細情報が必要になります。

7
Drax

したがって、他のすべてが指摘したように、returnは解決策ではありません。

私が最初に追加することは、これについて通常心配するべきではないということです。あなたの教授が明示的に尋ねない限り。適切なタイミングでのみクラスを削除することを外部クラスに信頼できないとしたら、それは非常に奇妙なことです。ポインターが渡される場合、ポインターはおそらくshared_ptr/weak_ptr、および適切なタイミングでクラスを破棄します。

しかし、ちょっとしたことですが、奇妙な問題が発生した場合、何かを学べば、どのように解決できるのか疑問に思うのは良いことです(そして、締め切りの間に時間を無駄にしないでください!)

それでは解決策はどうでしょうか?少なくともClass1のデストラクタを信頼してオブジェクトを早く破棄しない場合は、Class2のデストラクタをプライベートとして宣言し、Class1のデストラクタをClass2のフレンドとして宣言することができます。

class Class2;

class Class1 {

    Class2* myClass2;

public:
    ~Class1();
};

class Class2 {
private:
        ~Class2();

friend Class1::~Class1();
};

Class1::~Class1() {
    delete myClass2;
}

Class2::~Class2() {
}

ボーナスとして、「ステータス」フラグは必要ありません。これは良いことです-誰かがこの悪いことをあなたにねじ込んで欲しかったのなら、なぜ他の場所にステータスフラグをFINISHEDに設定してからdeleteを呼び出さないのですか?

これにより、Class1のデストラクタ以外の場所でオブジェクトを破棄できないという実際の保証があります。

もちろん、Class1のデストラクタはClass2のすべてのプライベートメンバーにアクセスできます。結局のところ、Class2はとにかく破壊されようとしています!しかし、もしそうなら、さらに複雑な方法でそれを回避することができます。何故なの。例えば:

class Class2;

class Class1 {
private:
    int status;

    Class2* myClass2;

public:
    ~Class1();
};

class Class2Hidden {
private:
      //Class2 private members
protected:
    ~Class2Hidden();
public:
    //Class2 public members
};

class Class2 : public Class2Hidden {
protected:
        ~Class2();

friend Class1::~Class1();
};

Class1::~Class1() {
    delete myClass2;
}

Class2Hidden::~Class2Hidden() {
}

Class2::~Class2() {
}

これにより、パブリックメンバーは派生クラスで引き続き使用できますが、プライベートメンバーは実際にはプライベートになります。 〜Class1は、Class2のプライベートおよび保護されたメンバー、およびClass2Hiddenの保護されたメンバーにのみアクセスできます。この場合はデストラクタのみです。 Class1のデストラクタからClass2の保護されたメンバーを保護する必要がある場合は、いくつかの方法がありますが、それは実際に何をしているのかに依存します。

幸運を!

1
Francesco Dondi

もちろん違います。 「return」の明示的な呼び出しは、デストラクタの実行後に暗黙的に戻ることと100%同等ではありません。

1
Trantor

オブジェクトを「自殺」させ、デストラクタを空のままにする新しいメソッドを作成することができます。そのため、次のような操作で目的の作業を実行できます。

Class1::~Class1()
{
    myClass2->status = FINISHED;
    myClass2->DeleteMe();
}

void Class2::DeleteMe()
{
   if (status == FINISHED)
   {
      delete this;
   }
 }

 Class2::~Class2()
 {
 }
1
Hasson

内部のすべてのスタックベースのオブジェクトは、デストラクタからreturnどれだけ早く破壊されます。 delete動的に割り当てられたオブジェクトを見逃すと、意図的メモリリークが発生します。

これは、move-constructorsがどのように機能するかという全体的な考えです。 CTORの移動では、元のオブジェクトのメモリが使用されます。元のオブジェクトのデストラクタは、単にdeleteを呼び出したり、呼び出したりすることはありません。

1
Ajay

いいえ。returnはメソッドの終了を意味するだけで、オブジェクトの破壊を停止しません。

また、なぜしたいのですか?オブジェクトがスタックに割り当てられ、なんとか破壊を停止することができた場合、オブジェクトはスタックの再生部分に存在し、おそらく次の関数呼び出しによって上書きされ、オブジェクトメモリ全体に書き込み、未定義の動作を作成する可能性があります。

同様に、オブジェクトがヒープに割り当てられ、破壊を防ぐことができた場合、deleteを呼び出すコードはオブジェクトへのポインタを保持する必要がないと想定するため、メモリリークが発生します。実際にはまだそこにあり、誰も参照していないメモリを占有しています。

1
Sean

この場合、delete演算子のクラス固有のオーバーロードを使用できます。 Class2の場合、次のようになります。

class Class2
{
    static void operator delete(void* ptr, std::size_t sz)
    {
        std::cout << "custom delete for size " << sz << '\n';
        if(finished)
            ::operator delete(ptr);
    }

    bool Finished;
}

その後、削除する前にfinishedをtrueに設定すると、実際の削除が呼び出されます。私はそれをテストしていないことに注意してください、私はここで見つけたコードを変更しました http://en.cppreference.com/w/cpp/memory/new/operator_delete

Class1::~Class1()
{
    class2->Finished = true;
    delete class2;
}
0