3

オブジェクトを明示的に破棄したい (オブジェクトとそのすべてのフィールドに対してデストラクタを呼び出す) が、問題のオブジェクトへのポインタをまだ保持している場合があります。したがって、私はまだメモリを解放したくありません。代わりに、「私は破壊されたオブジェクトです」という一種のフラグを残したいと思います。

私は次のアプローチのアイデアを思いつきました:

class BaseClass { //all objects in question derive from this class
public:
    BaseClass() : destroyed(false) {}
    virtual ~BaseClass() {}
private:
    bool destroyed;
public:
    bool isDestroyed() { return destroyed; }
    void destroy() {
        this->~BaseClass(); //this will call the virtual destructor of a derivative class
        new(this) BaseClass();
        destroyed=true;
    }
};

destroy呼び出されると、基本的に、持っていたオブジェクト (おそらく派生オブジェクト) を破棄し、まったく同じ場所に新しい「ゾンビ」オブジェクトを作成します。その結果、次のことを達成したいと考えています。

  • ptr以前にこのオブジェクトを指していた他のポインターはptr->isDestroyed()、その存在を確認するために呼び出すことができます。
  • ゾンビのフラグをチェックせずに派生オブジェクトに属するフィールドにアクセスしようとすると、悪いことが起こる可能性があることを認識しています
  • ゾンビ オブジェクトは、破壊されたオブジェクトと同じくらい多くのメモリを消費することを認識しています (の派生物である可能性があるためBaseClass) 。
  • 破壊されたオブジェクトのメモリを解放する必要があります。deleteしかし、その呼び出しがまだ正しいことを願っていますか?

質問:

上記のパターンを使用する際に考慮すべき他の問題はありますか?

ゾンビ オブジェクトを呼び出すdeleteと、以前の (通常の) オブジェクトによって消費されたメモリ全体が正しく解放されますか?


別の方法についてのご意見をお待ちしておりますが、上記のコードがもたらすすべてのリスクを理解したいと思います。

4

4 に答える 4

5

スマート ポインターを使用するという提案があります。実際、私はそうしていますが、私の参照は循環しています。本格的なガベージ コレクターを使用することもできますが、どこで (いつ!) サークル チェーンが壊れるかを知っているので、それを自分で利用したいと考えています。

次に、循環スマート ポインターの 1 つを明示的に null 化 (使用している場合はリセット) し、循環を断ち切ることができます。shared_ptr

または、サイクルがどこにあるかが事前にわかっている場合は、一部の の代わりに使用して事前に回避することもできます。weak_ptrshared_ptr

- - 編集 - -

弱いポインターのみによって参照されるオブジェクトが単に「無効」としてフラグが立てられ、そこに含まれるすべてのポインターに対する制御が解放される場合 (この文は明確ですか?)、私は満足します。

その後weak_ptr::expired、あなたを幸せにする必要があります:)

于 2012-11-10T12:49:50.463 に答える
5

あなたの質問に不快なコメントがありました。あなたが望むことをするためのより良い方法があるかもしれませんが、今私はそれらが値するとは思いません。どこから来たのかは理解していますが、実際には、書き込みを拒否するリセット機能を使用するのと同じ方法でデストラクタを使用しています。実際には、デストラクタ内で実際にコードを記述しない限り、デストラクタを呼び出すことは実際に何かを削除またはリセットすることとは関係がないため、デストラクタを呼び出しても何も得られません。

新しい配置についての質問について:

既にご存知かもしれませんが、プレースメント new はメモリを割り当てないため、それを呼び出すと同じ場所にオブジェクトが作成されるだけです。それがまさにあなたが望むものであることは理解していますが、それは必要ではありません. オブジェクトを破棄するだけで削除を呼び出さないため、クラスを初期化せずに破棄を true に設定できます。

要約すると:

  1. デストラクタを通常の仮想関数として使用すると、何も得られません。デストラクタが2回呼ばれると大変なことになるのでやらないでください
  2. プレースメント new の呼び出しはメモリを割り当てず、不必要な初期化を実行するだけです。destroy を true に設定するだけです。

やりたいことを正しく行い、デストラクタの利点を得るには、クラスの new および delete 演算子をオーバーロードし、通常の破棄メカニズムを使用する必要があります。次に、メモリを解放せずに無効としてマークするか、ほとんどのメモリを解放するが、ポインターがいくつかのフラグを指しているままにすることを選択できます。

編集

コメントに続いて、私が見たすべてのリスクと他の人が指摘したリスクを要約することにしました。

  1. マルチスレッド環境での無効なポインターへのアクセス: メソッドを使用すると、デストラクタが実行された後、破棄されたフラグが設定される前にクラスにアクセスできます (コメントの 1 つの質問について - shared_ptr はほとんどの目的でスレッドセーフです)。
  2. 完全に制御できない動作のリレー: メソッドは、デストラクタが動的に割り当てられていない他のメンバーのデストラクタを自動的に呼び出す方法に依存しています: これは、動的に割り当てられたメモリを具体的に解放する必要があることを意味します.まさにこれが実装されています。他のデストラクタが呼び出される順序を制御することはできません。
  3. 完全に制御できない動作の中継 (ポイント 2): コンパイラが他のデストラクタを呼び出すデストラクタの一部を実装する途中で中継しているため、コードが移植可能かどうか、またはどのように移植可能かさえもわかりません。 2 回呼び出して処理します。
  4. デストラクタは 2 回呼び出される可能性があります: 実装によっては、同じメモリを 2 回解放しないように保護しない限り、メモリ リークやヒープの破損が発生する可能性があります。プレースメント new を呼び出すことでそのケースを防ぐと主張します - ただし、マルチスレッド環境では、これはさらに保証されません。さらに、すべてのメモリ割り当てがデフォルトのコンストラクターによって行われると仮定します - 特定の実装に応じて、これが行われる場合とされない場合があります。真実。
  5. あなたは、あなたの質問に答えたり、それにコメントしたりしたすべての人のより良い判断に逆らっています - あなたは何か天才的かもしれませんが、おそらく、正しく機能する状況の小さなサブセットに実装を制限することで、自分自身を撃っているだけです. . 間違ったドライバーを使用すると、最終的にネジを損傷してしまうようなものです。同じように、意図しない方法で言語構造を使用すると、バグのあるプログラムになる可能性があります。デストラクタは、delete から、およびコンパイラによって生成されたコードから呼び出され、スタックをクリアすることを目的としています。直接使用するのは賢明ではありません。

そして、私は私の提案を繰り返します - あなたが望むもののためにdeleteとnewをオーバーロードします

于 2012-11-16T22:28:21.173 に答える
2

他のみんなと同じように、私はあなたがただ使うべきであることをお勧めしますweak_ptr。しかし、あなたはなぜあなたのアプローチがうまくいかないのかと尋ねました。洗練された実装と関心の分離の問題がいくつかありますが、それらについては議論しません。代わりに、あなたのコードはひどくスレッドセーフではないことを指摘しておきます。

2つの制御スレッドの次の実行シーケンスについて考えてみます。

// Thread One
if ( ! ptr -> isDestroyed() ) {     // Step One
    // ... interruption ...
    ptr -> something();             // Step Three

そして他:

// Thread Two
ptr -> destroy();                   // Step Two

ステップ3が発生するまでに、ポインターは無効になります。これを実装lock()などで修正することは可能ですが、ロックを解除しないことで不具合が発生する可能性があります。誰もが推奨している理由weak_ptrは、このクラス全体の問題が、クラスのインターフェースとその実装の両方で解決されているためです。

1つの問題が残っています。あなたは自由にオブジェクトを殺すことができる施設を望んでいるようです。これは、オブジェクトへのポインタのみが弱いものであり、オブジェクトが手動で削除されたときに壊れてしまうような強いものが存在しないことを要求することと同じです。(これは悪い考えではないことを規定しますが、なぜそれがあなたの場合に当てはまらないのかはわかりません。)との上に構築することでこれを得ることができweak_ptrますshared_ptr。これらのクラスは汎用であるため、へのshared_ptrアクセスを禁止する場合は、動作が異なるBaseClass特殊化を記述できます。shared_ptr<BaseClass>の1つのインスタンスを非表示にshared_ptr<BaseClass>して、削除を防ぎ、制御下のファクトリメソッドを介してそのようなポインタを提供します。

このモデルでは、destroy()注意が必要なセマンティクスです。最初の選択肢は、同期操作と非同期操作のどちらが必要かです。同期destroy()は、すべての外部ポインターが解放されるまでブロックし、新しいポインターの発行を許可しません。(ポインターでコピーコンストラクターが既に無効になっていると仮定します。)非同期には2種類ありますdestroy()。外部参照がまだ存在する場合、2つのうちの単純な方は失敗します。unique()隠しを呼び出すと、shared_ptr()これは簡単な実装になります。より複雑なものは、非同期I / O呼び出しのように機能し、将来のある時点で、おそらくすべての外部参照がなくなるとすぐに破棄が発生するようにスケジュールします。この関数は呼び出される可能性がありますmark_for_death()オブジェクトは戻り時に破棄される場合と破棄されない場合があるため、セマンティクスを反映します。

于 2012-11-17T00:12:48.390 に答える
1

代わりに、適切なスマートポインタパターンの1つを使用することを検討します。削除されたオブジェクトにアクセスする動作はまだ定義されておらず、「ゾンビ」フラグは実際には役に立ちません。削除されたオブジェクトインスタンスに関連付けられていたメモリは、作成された他のオブジェクトによってすぐに占有される可能性があるため、ゾンビフラグへのアクセスは信頼できる情報ではありません。

IMHO配置の新しいオペレーター

new(this) BaseClass();

あなたのdestroy()方法で使用されることは実際には役に立ちません。この方法の使用目的に少し依存します。派生オブジェクトまたは削除されたオブジェクトの内部デストラクタを削除する代わりに。後者の場合、メモリはとにかく解放されます。

アップデート:

あなたの編集によると、循環参照の発生を解決するために共有ポインター/弱いポインターのイディオムを使用する方が良いのではないでしょうか。そうでなければ、これらは設計上の欠陥と見なします。

于 2012-11-10T12:20:40.193 に答える