2

私はdelete thisc++での使用の可能性について考えていましたが、1つの使用法を見てきました。

オブジェクトがヒープ上にある場合にのみ言うことがdelete thisできるので、デストラクタをプライベートにして、オブジェクトがスタック上に作成されないようにすることができます。delete this結局、デストラクタとして機能するランダムなパブリックメンバー関数で言うことで、ヒープ上のオブジェクトを削除することができます。私の質問:

1)オブジェクトをスタックではなくヒープ上に強制的に作成したいのはなぜですか?

2)delete thisこれとは別に別の用途はありますか?(これが合法的な使用であると仮定します:))

4

9 に答える 9

7

を使用するスキームはdelete this、それを実行する関数を呼び出した人にはダングリングポインターが残るため、やや危険です。(もちろん、通常どおりオブジェクトを削除する場合も同様ですが、その場合、オブジェクトが削除されたことは明らかです)。それにもかかわらず、オブジェクトがそれ自体の存続期間を管理することを望む場合には、いくぶん正当なケースがあります。

これは、厄介で煩わしい参照カウントスキームを実装するために使用できます。オブジェクトへの参照を「取得」して削除されないようにし、終了したら「解放」して、他の誰も取得していない場合は次のように削除する機能があります。

class Nasty {
public:
    Nasty() : references(1) {}

    void acquire() {
        ++references;
    }
    void release() {
        if (--references == 0) {
            delete this;
        }
    }
private:
    ~Nasty() {}
    size_t references;
};

// Usage
Nasty * nasty = new Nasty; // 1 reference
nasty->acquire();          // get a second reference
nasty->release();          // back to one
nasty->release();          // deleted
nasty->acquire();          // BOOM!

std::shared_ptrスレッドセーフ、例外セーフであり、明示的なサポートを必要とせずにあらゆるタイプで機能し、削除後のアクセスを防ぐため、この目的で使用することをお勧めします。

さらに便利なことに、オブジェクトが作成されるイベント駆動型システムで使用し、オブジェクトが不要になったことを通知するイベントを受信するまで、オブジェクトを管理することができます。

class Worker : EventReceiver {
public:
    Worker() {
        start_receiving_events(this);
    }    
    virtual void on(WorkEvent) {
        do_work();
    }
    virtual void on(DeleteEvent) {
        stop_receiving_events(this);
        delete this;
    }
private:
    ~Worker() {}
    void do_work();
};
于 2012-09-07T11:10:44.640 に答える
4

1)オブジェクトをスタックではなくヒープ上に強制的に作成したいのはなぜですか?

1)オブジェクトの存続期間がスコープ(関数本体など)に論理的に関連付けられていないため。独自のライフスパンを管理する必要があるため、または本質的に共有オブジェクトであるため(したがって、そのライフスパンは、相互に依存するオブジェクトのライフスパンにアタッチする必要があります)。ここにいる人の中には、イベントハンドラー、タスクオブジェクト(スケジューラー内)、複雑なオブジェクト階層内の一般的なオブジェクトなどの例を指摘している人もいます。

2)割り当て/割り当て解除および構築/破棄のためにコードが実行される正確な場所を制御したいため。ここでの典型的なユースケースは、クロスモジュールコード(実行可能ファイルとDLL(または.soファイル)にまたがる)のユースケースです。バイナリ互換性とモジュール間の個別のヒープの問題があるため、多くの場合、これらの割り当て構築操作が発生するモジュールを厳密に制御する必要があります。これは、ヒープベースのオブジェクトのみを使用することを意味します。

2)これとは別にこれを削除する別の使用法はありますか?(これが合法的な使用であると仮定します:))

さて、あなたのユースケースは実際には「なぜ」ではなく単なる「ハウツー」です。もちろん、delete this;メンバー関数内でステートメントを使用する場合は、すべての作成を強制的に(およびステートメントが発生するnewのと同じ変換単位で)実行するためのコントロールを配置する必要があります。delete this;これを行わないと、スタイルが非常に悪くなり、危険になります。しかし、それはあなたがこれを使う「理由」に対処していません。

1)他の人が指摘しているように、正当なユースケースの1つは、ジョブがいつ終了したかを判断し、その結果として自分自身を破壊できるオブジェクトがある場合です。たとえば、イベントハンドラーは、イベントが処理されたときに自分自身を削除します。ネットワーク通信オブジェクトは、実行するように指定されたトランザクションが終了すると自分自身を削除します。スケジューラーのタスクオブジェクトは、タスクが完了したときに自分自身を削除します。しかし、これには大きな問題が残ります。それは、もはや存在しないことを外の世界に知らせることです。そのため、多くの人が「侵入参照カウント」スキームについて言及しています。これは、オブジェクトへの参照がなくなった場合にのみオブジェクトが削除されるようにする1つの方法です。別の解決策は、「有効な」オブジェクトのグローバル(シングルトンのような)リポジトリを使用することです。delete this;呼び出し(オーバーロードされたnew / deleteの一部として、またはすべてのnew / delete呼び出しと一緒に)。

ただし、経済的ではありませんが、同じ動作を実現するためのはるかに簡単で邪魔にならない方法があります。自己参照shared_ptrスキームを使用できます。そのように:

class AutonomousObject {
  private:
    std::shared_ptr<AutonomousObject> m_shared_this;

  protected:
    AutonomousObject(/* some params */);

  public:

    virtual ~AutonomousObject() { };

    template <typename... Args>
    static std::weak_ptr<AutonomousObject> Create(Args&&... args) {
      std::shared_ptr<AutonomousObject> result(new AutonomousObject(std::forward<Args>(args)...));
      result->m_shared_this = result;  // link the self-reference.
      return result;  // return a weak-pointer.
    };

    // this is the function called when the life-time should be terminated:
    void OnTerminate() {
      m_shared_this.reset( NULL );  // do not use reset(), but use reset( NULL ).
    };
};

上記(または、必要に応じて、この大まかな例のいくつかのバリエーション)を使用すると、オブジェクトは、必要であると見なされ、他の誰も使用していない限り、存続します。ウィークポインタメカニズムは、オブジェクトの外部ユーザーの可能性によって、オブジェクトの存在を照会するためのプロキシとして機能します。このスキームにより、オブジェクトは少し重くなります(共有ポインターが含まれます)が、実装はより簡単で安全です。もちろん、オブジェクトが最終的にそれ自体を削除することを確認する必要がありますが、それはこの種のシナリオでは当然のことです。

2)2番目のユースケースは、オブジェクトをヒープのみに制限する2番目の動機(上記を参照)と関連していると考えられますが、そのように制限しない場合にも当てはまります。割り当て解除と破棄の両方が正しいモジュール(オブジェクトが割り当てられて構築されたモジュール)にディスパッチされることを確認する場合は、動的ディスパッチ方法を使用する必要があります。そのためには、仮想関数を使用するのが最も簡単です。ただし、仮想デストラクタは、割り当て解除ではなく、破棄をディスパッチするだけなので、それをカットすることはありません。delete this;解決策は、問題のオブジェクトを呼び出す仮想の「破棄」関数を使用することです。これを実現するための簡単なスキームは次のとおりです。

struct CrossModuleDeleter;  //forward-declare.

class CrossModuleObject {
  private:
    virtual void Destroy() /* final */;

  public:
    CrossModuleObject(/* some params */);  //constructor can be public.

    virtual ~CrossModuleObject() { };  //destructor can be public.

    //.... whatever...

    friend struct CrossModuleDeleter;

    template <typename... Args>
    static std::shared_ptr< CrossModuleObject > Create(Args&&... args);
};

struct CrossModuleDeleter {
  void operator()(CrossModuleObject* p) const {
    p->Destroy();  // do a virtual dispatch to reach the correct deallocator.
  };
};

// In the cpp file:

// Note: This function should not be inlined, so stash it into a cpp file.
void CrossModuleObject::Destroy() {
  delete this;
};

template <typename... Args>
std::shared_ptr< CrossModuleObject > CrossModuleObject::Create(Args&&... args) {
  return std::shared_ptr< CrossModuleObject >( new CrossModuleObject(std::forward<Args>(args)...), CrossModuleDeleter() );
};

上記の種類のスキームは実際にはうまく機能し、派生クラスのこの仮想破壊メカニズムによる追加の侵入なしに、クラスが基本クラスとして機能できるという優れた利点があります。また、ヒープベースのオブジェクトのみを許可する目的で変更することもできます(通常のように、コンストラクタ-デストラクタをプライベートまたは保護します)。ヒープベースの制限がない場合でも、必要に応じてオブジェクトをローカル変数またはデータメンバーとして(値で)使用できるという利点がありますが、もちろん、使用する人が回避するための抜け穴が残ります。クラス。

私の知る限り、これらは私が今までどこでも見たり聞いたりした唯一の正当なユースケースです(そして最初のものは私が示したように簡単に回避でき、しばしばそうあるべきです)。

于 2012-09-07T15:22:48.020 に答える
3

一般的な理由は、オブジェクトの存続期間は、少なくともアプリケーションの観点から、クラスの内部の何らかの要因によって決定されるためです。したがって、を呼び出すプライベートメソッドである可能性がありますdelete this;

明らかに、オブジェクトが必要な時間を知る唯一のオブジェクトである場合、それをランダムなスレッドスタックに置くことはできません。そのようなオブジェクトをヒープ上に作成する必要があります。

于 2012-09-07T11:21:11.030 に答える
1

それは一般的に非常に悪い考えです。非常に少数のケースがあります。たとえば、COMオブジェクトが侵入参照カウントを強制している場合です。これは、非常に特殊な状況の理由でのみ行うことができます。汎用クラスでは決して行いません。

于 2012-09-07T11:14:40.567 に答える
1

1)オブジェクトをスタックではなくヒープ上に強制的に作成したいのはなぜですか?

その寿命はスコープルールによって決定されないためです。

2)これとは別にこれを削除する別の使用法はありますか?(これが合法的な使用であると仮定します:))

delete thisオブジェクトがそれ自体の寿命に責任を持つのに最適な場所にある場合に使用します。私が知っている最も簡単な例の1つは、GUIのウィンドウです。ウィンドウはイベントに反応します。そのサブセットは、ウィンドウを閉じて削除する必要があることを意味します。イベントハンドラーでは、ウィンドウは。を実行しdelete thisます。(処理をコントローラークラスに委任することもできます。ただし、「ウィンドウがイベントをコントローラークラスに転送し、ウィンドウを削除することを決定する」という状況はそれほど変わりませんdelete this。ウィンドウイベントハンドラーはウィンドウが削除されたままになります。クローズを削除から切り離す必要がありますが、あなたの論理的根拠は)の望ましさに関係しませんdelete this

于 2012-09-07T11:21:40.630 に答える
1
delete this;

時々役立つことがあり、通常、別のオブジェクトの存続期間も制御する制御クラスに使用されます。侵入参照カウントでは、制御しているクラスはそれから派生したクラスです。

このようなクラスを使用した結果、クラスのユーザーまたは作成者が生涯にわたって処理しやすくなります。これが達成されない場合、それは悪い習慣です。

正当な例としては、クラスが破棄される前に、それ自体へのすべての参照をクリーンアップする必要がある場合があります。このような場合、クラスへの参照を(おそらくモデルに)格納するときはいつでもクラスに「通知」し、終了時に、クラスはそれ自体を呼び出す前にこれらの参照などを無効にしdelete thisます。

これはすべて、クラスのユーザーの「舞台裏」で行われるはずです。

于 2012-09-07T11:51:36.737 に答える
1

「オブジェクトをスタックではなくヒープ上に強制的に作成したいのはなぜですか?」

一般に、そのようにしたいからではないことを強制する場合、それはクラスがポリモーフィック階層の一部であり、それを取得する唯一の正当な方法は、次のように異なる派生クラスのインスタンスを返すファクトリ関数からです。あなたがそれを渡すパラメータ、またはそれが知っているいくつかの構成に従って。そうすれば、ファクトリ関数がそれらを作成するように簡単に調整できますnew。これらのクラスのユーザーは、使用しているオブジェクトの派生型を事前に知らず、基本型のみを知っているため、必要な場合でもそれらをスタックに含めることはできません。

そのようなオブジェクトができたら、それらがで破壊されていることがわかり、delete最終的にはで終わる方法でそれらのライフサイクルを管理することを検討できますdelete this。これを行うのは、オブジェクトが不要になったときを何らかの方法で知ることができる場合のみです。これは、オブジェクトの有効期間を明示的に管理しないフレームワークの一部であるため、通常は(Mikeが言うように)そのコンポーネントに次のように伝えます。それらは切り離された/登録解除された/何でも[*]。

私の記憶が正しければ、ジェームズ・カンゼがあなたの男です。delete this覚えていないかもしれませんが、彼のデザインでは、単に使用されているだけでなく、一般的であると時々言及していると思います。このような設計では、共有所有権と外部ライフサイクル管理を回避し、独自のライフサイクルを管理するエンティティオブジェクトのネットワークを優先します。そして、必要に応じて、自分自身を破壊する前に、自分自身を知っているものから自分自身を登録解除します。したがって、「ツールベルト」に複数の「ツール」がある場合、ツールベルトが各ツールを「所有」しているとは、ツールがベルトに出入りすることを考えているとは解釈できません。

[*]それ以外の場合は、ファクトリにaを返すunique_ptrauto_ptr、呼び出し元にオブジェクトを選択したメモリ管理タイプに直接詰め込むように促すか、生のポインタを返しますが、ドキュメントを介して同じ励ましを提供します。あなたが見慣れているすべてのもの。

于 2012-09-07T11:51:42.157 に答える
0

経験則として、を使用しないことをお勧めしますdelete this

簡単に言えば、使用するものは、オブジェクトで実行されnewたときに使用するのに十分な責任を負う必要がdeleteあります。これにより、スタック/ヒープ上の問題も回避されます。

于 2012-09-07T11:07:15.030 に答える
0

昔々、私はいくつかのプラグインコードを書いていました。一部は高速である必要があるため、ビルド(プラグインのデバッグ、メインコードのリリース、またはその逆)を混合したと思います。または、別の状況が発生した可能性があります。このようなメインはすでにgccに基づいてリリースされており、プラグインはVCでデバッグ/テストされています。メインコードがプラグインから何かを削除したり、プラグインが何かを削除したりすると、メモリの問題が発生します。これは、両方が異なるメモリプールまたはmalloc実装を使用していたためです。だから私はプライベートdtorとdeleteThis()と呼ばれる仮想関数を持っていました。

-編集-今、私は削除演算子をオーバーロードするか、スマートポインターを使用するか、または単に関数を削除しないと述べることを検討するかもしれません。それは依存するでしょう、そしてあなたが本当にあなたが何をしているのかを本当に知らない限り(それをしないでください)、通常、new/deleteのオーバーロードは決して行われるべきではありません。deleteThis()を使用することにしたのは、Cのようなthing_allocとthing_freeの方が簡単で、deleteThis()の方がOOPの方法のように感じたからです。

于 2012-09-07T11:09:10.303 に答える