-1

私は、破壊されているオブジェクトのメソッドの呼び出しによって引き起こされる問題を、アプリケーションのクラッシュの原因となる問題を修正しようとしています。私は、次のクラスがあります削除されているオブジェクトの関数の呼び出しを防止する

// Forward declarations 
class A; 

class B 
{ 
public: 
    B(A* aPtr) : m_pA(aPtr) { Schedule(); } 
    ~B() { Timer::ref().Cancel(m_TimerId); } 

    void Schedule(); 
    void Update(); 

private: 
    A* m_pA; 
    int32_t m_TimerId; 
} 

void B::Schedule() 
{ 
    m_TimerId = Timer::ref().Schedule(1000, [this]() { Update(); }); 
} 

void B::Update() 
{ 
    m_pA->DoSomething(); 
} 

class A 
{ 
public: 
    void Create() { m_B = std::make_unique<B>(); } 
    void DoSomething() {} 

private: 
    std::unique_ptr<B> m_B; 
} 

基本的にクラスAは、AのBへの生PTRを格納するクラスBにunique_ptrタイマ、異なるスレッドにおけるランのシングルトンインスタンスを使用して保持し、そのスケジュール関数B ::更新()は1000ミリ秒ごとにそのスレッドで実行する。場合によっては、Aをスタック上のメンバ変数として格納するオブジェクトを破棄すると、Aが破壊されますが、B::Update()という別のスレッドから呼び出されたクラスBの中のぶら下がりポインタは、アプリケーションをクラッシュさせます。 nullptrです。このため、未定義の動作が発生します。

アプリケーションをクラッシュさせないために、これをどのように処理する必要がありますか?明示的に設定するとm_pA = nullptr; B::~B()で十分ですか?

+0

なぜ、スレッドはBのメンバーではないのですか? 〜Bはスレッドに参加して、無効なメモリがないことを確認できます。 – UKMonkey

+0

それはなぜでしょうか?タイマー、またはより具体的にはスケジューラーは、グローバル(貧しいデザインの選択肢、シングルトン、bla bla ...)であり、独自のスレッドを持っています... – mezo

+0

'まあまあ、そうでなければ、データのスコープを完全に定義していないために破棄されたオブジェクトを処理するスレッドが原因でクラッシュする – UKMonkey

答えて

1

m_pA = nullptrを明示的に設定しますか。 B ::〜B()で十分ですか?

いいえこれは、競合状態がまだ残っているだけでなく(スレッドが値を読み取り、ポインタがnullに設定され、オブジェクトが削除され、スレッドがオブジェクトを使用しようとすると)、削除したばかりのオブジェクトからメモリを読み込もうとします。

アプリケーションをクラッシュさせないために、これをどのように処理する必要がありますか?

(読み取り/書き込み場合)Bはスレッドよりも長い寿命を持っていることを確認します(これを行う最も簡単な方法は、Bが所有するスレッドを作成している)と、アクセスされた場合、すべての共有メモリが保護されていることを確認するために、 。削除されたデータに対してupdate()が呼び出されないようにするだけでなく、更新が呼び出されている間はデータを削除しないようにしてください。

+0

残念ながら、Bがスレッドを所有することはできません。これに別の解決策はありませんか? – mezo

+0

@ mezo Bとスレッドを別のものにして、そのオブジェクトが2のライフタイムが必要に応じて結ばれるようにします – UKMonkey

0

B::Update()でクラッシュすると、Timer::ref().Cancel()は、タイマーがスケジュールされた機能を実行している場合を含め、他のスレッドのタイマーを完全にキャンセルする前に戻ってきます。 TimerCancel()の仕組みを変更したり、別の方法を追加したりすることができれば、あなたのIDで本当にキャンセル/完了したことを保証します。こうすると、Aが破壊され、~B()が呼び出され、タイマーがキャンセルされ、Update()が呼び出されないか、少なくともUpdate()が完了するまで返されません。

しかし、あなたはそれを行うことができないと仮定すると、その後、私は考えることができる唯一のことは、 AB std::shared_ptrを作り、そしてスレッドその Bから std::weak_ptrを与え、そして中 weak_ptrからその shared_ptrを取得しようとしていますTimerのスケジュールされた関数コールバックこのように B::Update()は、 Bオブジェクトが生存している場合にのみ呼び出され、 Update()の期間生存します。次の問題はが m_pA->DoSomething();を呼び出すので Aを維持していることです。を Aに保ち、 Aを共有ポインタオブジェクトにすることも、 Bで処理することができます。しかし、そのいずれかの問題は、あなたが Bの構築時にタイマーを起動していることです.C++ 17より前には、構築中に共有オブジェクトから std::weak_ptrを得ることができません。だから、を Aで呼び出さなければなりません。

したがって、このような何か:

class A; 

class B : public std::enable_shared_from_this<B> 
{ 
public: 
    B() = default; 
    ~B() { Timer::ref().Cancel(m_TimerId); } 

    void Schedule(const std::shared_ptr<A>& aPtr); 
    void Update(); 

private: 
    std::weak_ptr<B> weak_from_this() 
    { 
     return shared_from_this(); 
    } 

private: 
    std::weak_ptr<A> wkA; 
    int32_t m_TimerId{0}; 
}; 

void B::Schedule(const std::shared_ptr<A>& aPtr) 
{ 
    wkA = aPtr; 
    m_TimerId = Timer::ref().Schedule(1000, 
        [myself = weak_from_this()]() 
        { 
         if (auto me = myself.lock()) 
         { 
          me->Update(); 
         } 
        }); 
} 

void B::Update() 
{ 
    if (auto pA = wkA.lock()) 
    { 
     pA->DoSomething(); 
    } 
} 

class A : public std::enable_shared_from_this<A> 
{ 
public: 
    void Create() 
    { 
     m_B = std::make_shared<B>(); 
     m_B->Schedule(shared_from_this()); 
    } 

    void DoSomething() {} 

private: 
    std::shared_ptr<B> m_B; 
}; 

注上記は、C++ 17までのみ必要ないくつかのことをやったこと。また、これはあなたのAクラスのユーザーが共有オブジェクトとして作成する必要があり、望ましくない可能性があることを意味します。

関連する問題