2012-04-22 8 views
4

伝統的なポインタを使ってnewで何かを割り当てたときは、C++でdelete thisと言うことができます。実際には、それを注意深く扱うのが良い方法であることも知っています。オブジェクトがstd::shared_ptrで保持されている場合、delete thisと言うことができますか?そして、デストラクタを呼び出すべきですよね?あなたにアイデアを伝えるために、私は船がミサイルを撃つことができるゲームを作っています。ミサイルを自分自身で削除したいのです。"delete this"をstd :: shared_ptrで割り当てられたオブジェクトに置き換えますか?

+3

オブジェクトを削除するには、 'delete this'を使用するのが良い方法ではありません。あなたがそれを慎重に扱わなければならないという事実は、それを示すのに十分です。それは生涯管理を手配する間違いやすい方法です。 – bames53

答えて

12

いいえ、オブジェクトの寿命はshared_ptrのホルダーによって決定されるため、オブジェクト自体が死ぬかどうかを判断できません。あなたがそうするならば、最後にshared_ptrが死んだときにはダブル が削除されます。私が提供できる唯一の解決策は、あなたの設計を再考することです(最初はshared_ptrが必要ではなく、ミサイルはおそらく値やプールされたオブジェクトかもしれません)。

3

ミサイル自体を削除するには、ミサイル自体を所有しているか、少なくともミサイルの所有権を他人と共有する必要があります。 shared_ptrがミサイルにあると言っているので、あなたは既にミサイルの所有権を共有する複数のオブジェクトを持っていると仮定しています。

ミサイルはshared_ptrを自分自身に持っていて、それ自体の所有権を共有することは可能です。しかし、これは常に周期的な所有権パターンを作り出します。ミサイルのshared_ptrデータメンバーが参照する限り、参照カウントはゼロに決して落ちることはありません。したがって、ミサイルはリークされます。

あなたは外部のオブジェクトやイベントでミサイルに自分自身を削除するよう指示することができますが、その点が何であるか分かりません。ミサイルに自分自身を削除するように伝えるには、その通信はshared_ptrを介して行われなければなりません。その後、削除することは実際に起こりません。shared_ptrはミサイルを運ぶことができます。

はい、可能です。いいえ、私はそれが良い考えだとは思わない。それは私にメモリリークが発生する傾向があり、実際に価値を追加しません。しかし、好奇心のために、ここにあなたがそれを行うだろうかです:

#include <iostream> 
#include <memory> 

class missile 
    : public std::enable_shared_from_this<missile> 
{ 
    std::shared_ptr<missile> self_; 
public: 
    missile() 
     {} 

    ~missile() {std::cout << "~missile()\n";} 

    void set_yourself() 
    { 
     self_ = shared_from_this(); 
    } 
    void delete_yourself() 
    { 
     if (self_) 
      self_.reset(); 
    } 
}; 

int main() 
{ 
    try 
    { 
     std::shared_ptr<missile> m = std::make_shared<missile>(); 
     m->set_yourself(); 
     std::weak_ptr<missile> wp = m; 
     std::cout << "before first reset()\n"; 
     m.reset(); 
     std::cout << "after first reset()\n"; 
     // missile leaked here 
     m = wp.lock(); 
     m->delete_yourself(); 
     std::cout << "before second reset()\n"; 
     m.reset(); // missile deleted here 
     std::cout << "after second reset()\n"; 
    } 
    catch (const std::exception& e) 
    { 
     std::cout << e.what() << '\n'; 
    } 
} 
+0

投票が必要かどうかわかりません。解決策は正確で斬新ですが、問題は間違っています。 –

+0

どのようにメモリリークがありますか? 'wp'は期限切れではありません。生ポインタを扱うときとまったく同じ責任があります。それが自分自身を削除している間ミサイルを使用している場合は、あなたの足元から引き出した靴を持っていないボーナスで。 –

+0

@エミリーL:メモリリークが起こりやすいと言いました。ミサイルに自分自身を削除するよう伝えるのを怠ると、それが漏れる。この例は、 'delete_yourself'を呼び出さずにすべての外部所有者を削除することによって証明しています。この例では、外部所有者を再設定し、ミサイルに自分自身を削除するように指示し、外部所有者が所有権を放棄すると削除されます。私。なぜ外部のオーナーがいないのですか?より一般的なイディオムは、ミサイルが自分自身に 'weak_ptr'を維持し、その' weak_ptr'を使って外部エンティティに強力な所有権を渡すことです。 –

0

を私はレイトショーのだが、私はちょうど今、この自分自身をやりたいと思っに走ったし、それが「であるこ​​とに気づい知っ種類・オブ可能ですが、あなたはいくつかのことをする必要があります。

ハワードの答えは正しい軌跡ですが、元のshared_ptrの構築をクライアントに委ねてはいけないので、マークを忘れてしまいます。これは、メモリリークのリスクを開放するものです。代わりに、構造体をカプセル化し、弱いポインタのみを許可する必要があります。削除されたオブジェクトを参照しているすべてのMissilePtrが満了する付加的な利点を有するdelete thisとして意味的に同じ効果をもたらすdie()を呼び出す

class Missile{ 
private: 
    Missile(...){ }; // No external construction allowed 
    Missile(const Missile&) = delete; // Copying not allowed 
    void operator = (const Missile&) = delete; // -||- 

    std::shared_ptr<Missile> m_self; 
public: 
    template<typename... Args> 
    static MissilePtr makeMissile(Args... args){ 
     auto that = std::make_shared<Misile>(args...); 
     that.m_self = that; // that holds a reference to itself (ref count = 2) 
     return that; // 'that' is destroyed and ref-count reaches 1. 
    } 

    void die(){ 
     m_self.reset(); 
    } 

    ... 
}; 

typedef std::weak_ptr<Missile> MissilePtr; 

void useMissile(MissilePtr ptr){ 
    auto missile = ptr.lock(); // Now ptr cannot be deleted until missile goes out of scope 
    missile->die(); // m_self looses the reference but 'missile' still holds a reference 
    missile->whatever(); // Completely valid. Will not invoke UB 
} // Exiting the scope will make the data in missile be deleted. 

:ここ

は一例です。また、MissilePtrのいずれかがthisにアクセスするために使用されている場合、そのアクセスに使用された一時的なstd::shared_ptrが破壊されるまで削除が遅延され、生涯の頭痛を軽減します。

しかし、必ず少なくとも1つを常にMissilePtrにし、ある時点でdie()を呼び出してください。そうしないと、メモリリークが発生します。ちょうどあなたが普通のポインタを使っているのと同じです。

1

この質問はかなり古いですが、同様の問題がありました(このケースでは、弱いポインタを共有できる一方で、ライフサイクルを管理しなければならない「リスナー」オブジェクト)、解決策を提示していませんでした私のため、私は仮定して、私が見つけた解決策を共有していますその:

  • オブジェクトを使用すると、shared_ptrのは、同様の ソリューションです必要がある場合、それは(自身のライフサイクルだ、ひいては share_ptrを共有することはありませんが、weak_ptrを管理します+ use_shared_from_thisはそれを行うことができます)。
  • RAIIを破る悪い考えであるので、我々はそれを行うことはありません。私たちは アドレスここでメンバーshare_ptrを含有するものとして、オブジェクト 自身が所有するshared_ptrを持っていることの問題が何であるかは に、二重のコールにつながります(通常は オブジェクト破棄時に1回、自己 にshared_ptrメンバーが含まれていた場合には2回目に呼び出されます)、デストラクタが2回呼び出されるときに、オブジェクトの破棄と通常はクラッシュ(または少なくとも定義されていない の動作)

コード:

#include <memory> 
#include <stdio.h> 

using std::shared_ptr; 
using std::weak_ptr; 

class A { 
    struct D { 
      bool deleted = false; 
      void operator()(A *p) { 
       printf("[deleter (%s)]\n", p, deleted ? "ignored":"deleted"); 
       if(!deleted) delete p; 
     } 
    }; 

    public: shared_ptr<A> $ptr = shared_ptr<A>(this, D()); 

    public: ~A() { 
     std::get_deleter<A::D>($ptr)->deleted = true; 
    } 

    public: weak_ptr<A> ptr() { return $ptr; } 
}; 

void test() { 
    A a; 

    printf("count: %d\n", a.ptr().lock().use_count()); 
    printf("count: %d\n", a.ptr().use_count()); 
} 

int main(int argc, char *argv[]) { 
    puts("+++ main"); 

    test(); 

    puts("--- main"); 
} 

出力:

$ g++ -std=c++11 -o test test.cpp && ./test 
+++ main 
count: 2 
count: 1 
[deleter (ignored)] 
--- main 

shared_ptrの削除手段がスタックに割り当てられたオブジェクトに対して呼び出されることはありませ飽きないはずですので、それは通常のオブジェクトの破壊でない場合には、それだけでバイパス削除(既定のオブジェクトデストラクタが既に呼び出されているので、この点まで到達しました)。

関連する問題