web-dev-qa-db-ja.com

C ++のデストラクタからオブジェクトを復活させますか?

免責事項:私はこれが悪い設計であることを知っています。C++でデストラクタがどのように機能するかについてより深い知識を得ようとするために、私は好奇心から質問をしています。

C#では、クラスのデストラクタにGC.KeepAlive(this)を記述できます(以下の編集を参照)。これは、デストラクタ呼び出しが完了した後でも、オブジェクトはメモリ内で引き続き有効です。

C++の設計では、上記のC#アルゴリズムと同様に、デストラクタからオブジェクトを復活させることができますか?

編集:以下の回答で指摘されているように、GC.ReRegisterForFinalize()GC.KeepAlive(this)よりも質問に密接に関連しています。

49
MathuSum Mut

簡単に言えば、いいえ。 C++は、JavaまたはC#のようなガベージコレクションを採用していません。オブジェクトが破棄されると、オブジェクトはすぐに破棄されます。完全に消えます。合唱に参加できません。フィヨルドのピン留めなど...

そして、これを数回に分けて別の言葉で言うと、簡単に再解釈することはできません...

デストラクタは、オブジェクト破棄の一部として呼び出されます。オブジェクトの破棄は、デストラクタの呼び出しと、オブジェクト自体に使用されたメモリの割り当て解除で構成されます。これは単一のプロセスであり、2つの別個のプロセスではありません。デストラクタが実行されている間、デストラクタが使用するオブジェクトはまだ存在しますが、借用時間に存在します。デストラクタが戻るとすぐにオブジェクトが気化するというのは、先に述べた結論です。デストラクタが呼び出されると、オブジェクトは破棄され、その運命を変えるものは何もありません。

これを理解してください。デストラクタが呼び出されている理由は、オブジェクトが「new」でヒープに最初に割り当てられ、現在「delete」されているためです。 「削除」は「削除」を意味し、「多分削除」ではありません。したがって、オブジェクトは削除されます。または、オブジェクトがスタックに割り当てられた場合、実行スレッドはスコープを終了したため、スコープで宣言されたすべてのオブジェクトが破棄されます。デストラクタは、技術的には、オブジェクトが破棄された結果として呼び出されます。したがって、オブジェクトは破棄されています。終わり。

そうは言っても、C++ではクラスにカスタムアロケーターを実装できます。必要に応じて、必要な機能を実装する独自のカスタムメモリ割り当て関数と割り当て解除関数を作成できます。これらはスタックに割り当てられたオブジェクト(つまり、ローカル変数)には使用されません。

100
Sam Varshavchik

あなたは実際に_GC.KeepAlive_が.NETで何をするかを誤って伝えています。オブジェクトが破棄されないようにするために、オブジェクトのデストラクタで使用しないでください。実際には、GC.KeepAlive()は空であり、実装されていません。 .NETソースコード here を参照してください。

これは、パラメーターとして渡されたオブジェクトがガベージコレクションされないようにしますbefore_GC.KeepAlive_への呼び出しが発生します。パラメータとしてKeepAliveに渡されたオブジェクトは、_GC.KeepAlive_の呼び出し直後にガベージコレクションできます。 KeepAliveには実際の実装がないため、これは純粋に、コンパイラがKeepAliveにパラメータとして渡されるオブジェクトへの参照を維持する必要があるという事実に基づいて行われます。オブジェクトをパラメーターとして受け取る他の関数(コンパイラーまたはランタイムによってインライン化されていない)も代わりに使用できます。

52
NineBerry

ここにアイデアがあります:

C* gPhoenix= nullptr;

C::~C ()
{
gPhoenix= new C (*this);  // note: loses any further-derived class ("slice")
}

ここで、関係するオブジェクト(ベースまたはメンバー)に実際に何かを行うデストラクタがある場合、delete gPhoenix;したがって、実際に達成しようとしていることに応じて、より複雑なメカニズムが必要になります。しかし、実際の目標はなく、奇妙な探査だけなので、これを指摘するだけで十分です。

デストラクタの本体が呼び出されても、オブジェクトは完全に良好です。デストラクタ内から通常のメンバー関数呼び出しを行うと、それは完全に重要で正常に見えます。

オブジェクトを所有しているメモリは解放されるため、その場に留まることはできません。そして、体を離れた後、他の破壊は自動的に行われ、干渉することはできません。ただし、その前にオブジェクトを複製できます。

8
JDługosz

すでに指摘されている と同様に、 GC.KeepAlive はそれを行いません。

.NETが進む限り、ファイナライザから GC.ReRegisterForFinalize を使用して復活させることが可能ですが、 WeakReference または GCHandle 復活の追跡、または単にthisをクラス外の何かに与えます。そうすることは破壊を打ち切るでしょう。

これは.NET 2.0でガベージコレクションを検出する古いトリックです 関連なし でも機能します(ガベージコレクションが部分的になり、他のスレッドと並行して実行できるようになります)。

.NETではファイナライザを使用しているため、破棄の前に実行され、それを防ぐことができることに重点を置く必要があります。したがって、破壊後にオブジェクトを回復できないことは技術的には正しいですが、 任意の言語で -代わりにGC.ReRegisterForFinalizeを使用する以外は、.NETで記述した動作に近づくことができます。


C++では、すでに 正解 が与えられています。

6
Theraot

それはどの言語でも不可能です。

あなたの理解は少しずれています。 GC.KeepAliveは、オブジェクトをガベージコレクターで収集できないものとしてマークします。これは、ガベージコレクション戦略がオブジェクトを破壊するのを防ぎます。オブジェクトがガベージコレクターが使用状況を追跡できないアンマネージコードで使用されている場合に役立ちます。これは、オブジェクトが破棄された後にメモリ内にあるという意味ではありません。

オブジェクトが破棄を開始すると、コードはリソース(メモリ、ファイルハンドラー、ネットワーク接続)を解放します。順序は通常、最も深い派生クラスから基本クラスに戻ります。途中で何かが破壊を防ぐためのものだった場合、これらのリソースが再取得され、オブジェクトが一貫性のない状態になるという保証はありません。

より可能性が高いのは、std::shared_ptrコピーと参照を追跡し、オブジェクトが不要になった場合にのみオブジェクトを破棄します。

4
Sorin

それが役立つ場合、デストラクタ関数とメモリ割り当ては異なります。

デストラクタは単なる関数です。明示的に呼び出すことができます。それが破壊的なことを何も行わない場合、それを再度呼び出すこと(たとえば、オブジェクトがスコープから外れたり、削除されたりした場合)は、非常に奇妙ですが、必ずしも問題があるわけではありません。おそらくこれを標準で扱っているセクションがあるでしょう。以下の例を参照してください。たとえば、一部のSTLコンテナはオブジェクトの有効期間とメモリ割り当てを個別に管理するため、明示的にデストラクタを呼び出します。

通常、コンパイラは、自動変数がスコープから外れるか、ヒープに割り当てられたオブジェクトが削除によって破棄されると、デストラクタを呼び出すコードを挿入します。このメモリの割り当て解除は、デストラクタ内では変更できません。

New演算子の追加の実装を提供するか、newの配置などの既存の実装を使用することにより、メモリ割り当てを担当できますが、一般的なデフォルトの動作は、コンパイラがデストラクタを呼び出すことであり、片付けるチャンスです。一部のメモリが後でクリアされるという事実は、デストラクタの制御外です。

#include <iostream>
#include <iomanip>

namespace test
{
  class GotNormalDestructor
  {
    public:
      ~GotNormalDestructor() { std::wcout << L"~GotNormalDestructor(). this=0x" << std::hex << this << L"\n"; }
  };

  class GotVirtualDestructor
  {
    public:
      virtual ~GotVirtualDestructor() { std::wcout << L"~GotVirtualDestructor(). this=0x" << std::hex << this << L"\n"; }
  };

  template <typename T>
  static void create_destruct_delete(wchar_t const name[])
  {
    std::wcout << L"create_destruct_delete<" << name << L">()\n";
    {
      T t;
      std::wcout << L"Destructing auto " << name << L" explicitly.\n";
      t.~T();
      std::wcout << L"Finished destructing " << name << L" explicitly.\n";
      std::wcout << name << L" going out of scope.\n";
    }
    std::wcout << L"Finished " << name << L" going out of scope.\n";
    std::wcout << L"\n";
  }

  template <typename T>
  static void new_destruct_delete(wchar_t const name[])
  {
    std::wcout << L"new_destruct_delete<" << name << L">()\n";
    T *t = new T;
    std::wcout << L"Destructing new " << name << L" explicitly.\n";
    t->~T();
    std::wcout << L"Finished destructing new " << name << L" explicitly.\n";
    std::wcout << L"Deleting " << name << L".\n";
    delete t;
    std::wcout << L"Finished deleting " << name << L".\n";
    std::wcout << L"\n";
  }

  static void test_destructor()
  {
    {
      std::wcout << L"\n===auto normal destructor variable===\n";
      GotNormalDestructor got_normal;
    }

    {
      std::wcout << L"\n===auto virtual destructor variable===\n";
      GotVirtualDestructor got_virtual;
    }

    {
      std::wcout << L"\n===new variables===\n";
      new_destruct_delete<GotNormalDestructor>(L"GotNormalDestructor");
      new_destruct_delete<GotVirtualDestructor>(L"GotVirtualDestructor"); 
    }

    {
      std::wcout << L"\n===auto variables===\n";
      create_destruct_delete<GotNormalDestructor>(L"GotNormalDestructor");
      create_destruct_delete<GotVirtualDestructor>(L"GotVirtualDestructor");
    }

    std::wcout << std::endl;
  }
}

int main(int argc, char *argv[])
{
  test::test_destructor();

  return 0;
}

出力例

===auto normal destructor variable===
~GotNormalDestructor(). this=0x0x23fe1f

===auto virtual destructor variable===
~GotVirtualDestructor(). this=0x0x23fe10

===new variables===
new_destruct_delete<GotNormalDestructor>()
Destructing new GotNormalDestructor explicitly.
~GotNormalDestructor(). this=0x0x526700
Finished destructing new GotNormalDestructor explicitly.
Deleting GotNormalDestructor.
~GotNormalDestructor(). this=0x0x526700
Finished deleting GotNormalDestructor.

new_destruct_delete<GotVirtualDestructor>()
Destructing new GotVirtualDestructor explicitly.
~GotVirtualDestructor(). this=0x0x526700
Finished destructing new GotVirtualDestructor explicitly.
Deleting GotVirtualDestructor.
~GotVirtualDestructor(). this=0x0x526700
Finished deleting GotVirtualDestructor.


===auto variables===
create_destruct_delete<GotNormalDestructor>()
Destructing auto GotNormalDestructor explicitly.
~GotNormalDestructor(). this=0x0x23fdcf
Finished destructing GotNormalDestructor explicitly.
GotNormalDestructor going out of scope.
~GotNormalDestructor(). this=0x0x23fdcf
Finished GotNormalDestructor going out of scope.

create_destruct_delete<GotVirtualDestructor>()
Destructing auto GotVirtualDestructor explicitly.
~GotVirtualDestructor(). this=0x0x23fdc0
Finished destructing GotVirtualDestructor explicitly.
GotVirtualDestructor going out of scope.
~GotVirtualDestructor(). this=0x0x23fdc0
Finished GotVirtualDestructor going out of scope.
3
WaffleSouffle