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() );
};
上記の種類のスキームは実際にはうまく機能し、派生クラスのこの仮想破壊メカニズムによる追加の侵入なしに、クラスが基本クラスとして機能できるという優れた利点があります。また、ヒープベースのオブジェクトのみを許可する目的で変更することもできます(通常のように、コンストラクタ-デストラクタをプライベートまたは保護します)。ヒープベースの制限がない場合でも、必要に応じてオブジェクトをローカル変数またはデータメンバーとして(値で)使用できるという利点がありますが、もちろん、使用する人が回避するための抜け穴が残ります。クラス。
私の知る限り、これらは私が今までどこでも見たり聞いたりした唯一の正当なユースケースです(そして最初のものは私が示したように簡単に回避でき、しばしばそうあるべきです)。