2015-02-06 13 views
5

Also habe ich diese Funktion, um Listener hinzuzufügen und es konvertiert gemeinsame Zeiger einer Klasse, so dass ich es später aufrufen kann, wenn ich eine Benachrichtigung bekomme.C++ weak_ptr.lock() segfault

void registerListener(std::shared_ptr<T> listener) 
{ 
    if (!listener) { 
     qCWarning(OBSERVER_LOGGER) << "Attempted to register a null observer."; 
     return; 
    } 
    // TODO make a foreach function that removes dead listeners to get rid of this code dupe 
    for (auto iter=listeners.begin(); iter != listeners.end();) { 
     if (auto shared = iter->lock()) { 
      if (listener == shared) { 
       return; 
      } 
      iter++; 
     } else { 
      iter = listeners.erase(iter); 
     } 
    } 
    auto weak = std::weak_ptr<T>(listener); 
    listeners.push_back(weak); 
} 

void notify(std::function<void(std::shared_ptr<T>)> onNotify) 
{ 
    // TODO make a foreach function that removes dead listeners to get rid of this code dupe 
    for (auto iter=listeners.begin(); iter != listeners.end();) { 
     if (auto shared = iter->lock()) { 
      onNotify(shared); 
      iter++; 
     } else { 
      iter = listeners.erase(iter); 
     } 
    } 
} 

private: 
std::vector<std::weak_ptr<T>> listeners; 

und aus irgendeinem Grund "ITER> lock()" segfaults. Ich werde sagen, dass dies eine Qt-Anwendung ist, aber ich habe absichtlich KEINE Threads erstellt (von denen ich weiß), so dass ich nur sehr verwirrt bin, was ich falsch mache, um diese weak_ptrs zu brechen. Also, wenn ich es in gdb laufen lasse, funktioniert es gut. Aber wenn ich setze, "disable-randomization off" dann bekomme ich den Fehler. Ich denke, das ist ein seltsames Problem mit nicht initialisierten Variablen. Wenn es hilft, ist das der Stack wenn ich in gdb stürze.

#0 0x00007f856bd8beec in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_get_use_count() const() 
#1 0x00007f856bd844a8 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_add_ref_lock_nothrow()() 
#2 0x00007f856bd9cd7d in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count(std::__weak_count<(__gnu_cxx::_Lock_policy)2> const&, std::nothrow_t)() 
#3 0x00007f856bda9948 in std::__shared_ptr<IEntityListener<Assignment>, (__gnu_cxx::_Lock_policy)2>::__shared_ptr(std::__weak_ptr<IEntityListener<Assignment>, (__gnu_cxx::_Lock_policy)2> const&, std::nothrow_t)() 
#4 0x00007f856bda8a62 in std::shared_ptr<IEntityListener<Assignment> >::shared_ptr(std::weak_ptr<IEntityListener<Assignment> > const&, std::nothrow_t)() 
#5 0x00007f856bda701a in std::weak_ptr<IEntityListener<Assignment> >::lock() const() 
#6 0x00007f856bda5624 in Observer<IEntityListener<Assignment> >::notify(std::function<void (std::shared_ptr<IEntityListener<Assignment> >)>)() 
#7 0x00007f856bda3a1a in EntityObserver<Assignment>::notifyCreated(std::shared_ptr<Assignment>)() 

EDIT: Michael Burr geschrieben, die Möglichkeit der Zuhörer während neue Hörer sich eintragen, wo hinzugefügt wird, die völlig passieren könnte. Dies würde dazu führen, dass der Iterator ungültig ist, und wenn ich gehe, um weak_ptr.lock() für einen Speicherabschnitt aufzurufen, der kein weak_ptr ist, BOOM. Ich denke, hier ist eine Moral, irgendwo muss ich sie finden.

+2

Können Sie uns ein komplettes minimales Arbeitsprogramm mit einem main()? –

+0

Sie könnten 'auto weak = std :: weak_ptr (Listener);' zu 'std :: weak_ptr weak (Listener);'. Außerdem ist es * sehr * verwirrend, wie Sie eine Funktion namens 'notify()' haben, die ein Argument mit dem gleichen Namen verwendet - was auch eine Funktion ist! –

+0

Ich kann versuchen, später habe ich eigentlich kein einfacheres Programm, das noch stürzt. Das Ding, das keinen Sinn macht, ist, dass das für die letzten 6 Monate funktionierte, und dann habe ich einen neuen Hörer und BOOM hinzugefügt, plötzlich explodiert alles. Aber ich ging sogar so weit, den neuen Zuhörer zu differieren, und das ist GENAU DAS GLEICHE! – Buttink

Antwort

4

Wenn notify() genannt wird, ist es möglich, dass die Funktion durch das onNotify() Funktion Objekt aufgerufen in registerListener() führen wird indirekt aufgerufen wird (oder ein anderer Code, der hinzufügen oder entfernen Sie Einträge in der listeners Sammlung)?

Wenn ja, kann die iter, die in der notify()for Schleife verwendet wird, ungültig gemacht werden. Vielleicht möchten Sie notify() ändern etwas wie die folgenden sehen, die alle shared_ptr Objekte Warteschlangen so zu informieren, dass es keine Rolle spielt, ob die listeners Sammlung während einer der onNotify() Rückrufe modifiziert:

#include <queue> 

void notify(std::function<void(std::shared_ptr<T>)> onNotify) 
{ 
    std::queue<std::shared_ptr<T>> notify_targets; 

    for (auto iter=listeners.begin(); iter != listeners.end();) { 
     if (auto shared = iter->lock()) { 
      notify_targets.push(shared); 
      iter++; 
     } else { 
      iter = listeners.erase(iter); 
     } 
    } 

    while (!notify_targets.empty()) { 
     onNotify(notify_targets.front()); 
     notify_targets.pop(); 
    } 

} 
+0

Solche Schleifen können auch verhindert werden, indem sichergestellt wird, dass Ereignis-Listener nur const-Zeiger auf das beobachtbare Objekt zusammen mit registerListener() erhalten, was keine konstante Operation ist. – BitTickler

+0

JA! Das hat es behoben. Ich weiß nicht, warum ich das vorher nicht gesehen habe. Ich fühle mich wieder wie ein Idiot. Wir hatten genau das gleiche Problem auch in einer Android-App. Anmerkung zur Selbstauslöschung eines Iter WIRKLICH WIRKLICH. Ich habe diese QML-App vollständig async statt Multithread gehalten, so dass ich nicht daran dachte, die Liste zu modifizieren, während ich die Hörer benachrichtigen würde. – Buttink

+0

@Buttink: Es ist eine sehr einfache Sache zu verpassen, da die Operation, die den Iterator ungültig macht, außer Sichtweite ist. Es hilft nicht, dass es eine Reihe von Regeln gibt, wenn Iteratoren ungültig gemacht werden. Wie auch immer, MSVC hat eine sehr gute Iterator-Gültigkeitsverfolgung in ihren Debug-Builds, aber manchmal kann die Konfiguration davon verwirrend sein (Suche nach '_ITERATOR_DEBUG_LEVEL' und' _HAS_ITERATOR_DEBUGGING'). Mir ist keine ähnliche Iterator-Debugging-Unterstützung in der Toolchain von GCC bekannt. –

1

Ich kann kein offensichtliches Problem in dem Code finden, den Sie zeigen. Ich nehme an, das Problem liegt in dem Code, den Sie nicht zeigen. Bedenken Sie, dass Q_OBJECT-geschmückte Objekte auch eine qt-bezogene Lebenszyklusverwaltung haben. Vielleicht gibt es ein paar Störungen ...

Vielleicht hilft es, wenn Sie Ihren Code mit dem Code hier posten und Sie schnell den entscheidenden Unterschied finden.

#include <vector> 
#include <algorithm> 
#include <memory> 

template <typename _Observable> 
class IObserver 
{ 
public: 
    virtual ~IObserver() {} 
    virtual void OnChange(const _Observable* what) = 0; 
}; 

template <class T> 
class CObservable 
    : public IObserver<T> // Make sure this class breaks once IObserver<> changes. 
{ 
public: 
    typedef IObserver<T> Observer_t; 
    typedef std::weak_ptr<Observer_t> Observer_reference; 
    typedef std::shared_ptr<Observer_t> Observer_strong_reference; 
    typedef T Class_t; 
    typedef std::vector<Observer_reference> ObserverRefCollection; 
private: 

    ObserverRefCollection m_observers; 

    void CleanupZombies() 
    { 
     m_observers.erase(std::remove_if(m_observers.begin(), m_observers.end(), 
      [this](Observer_reference iter) -> bool 
     { 
      Observer_strong_reference o = iter.lock(); 
      return !o; 
     } 
     ), m_observers.end()); 
    } 
public: 
    void RegisterObserver(Observer_strong_reference& observer) 
    { 
     if (!observer) 
      return; 
     for (auto& iter : m_observers) 
     { 
      if (observer == iter.lock()) 
       return; 
     } 
     m_observers.push_back(Observer_reference(observer)); 
    } 

    /*virtual*/ void OnChange(const Class_t* what) 
    { 
     bool hasZombies = false; 
     for (auto& iter : m_observers) 
     { 
      Observer_strong_reference o = iter.lock(); 
      if (o) 
      { 
       o->OnChange(what); 
      } 
      else 
      { 
       hasZombies = true; 
      } 
     } 
     if (hasZombies) 
      CleanupZombies(); 
    } 
}; 

class CObservableUint32 
    : public CObservable<CObservableUint32> 
{ 
    uint32_t m_value; 
public: 
    void Set(uint32_t newValue) 
    { 
     bool changed = newValue != m_value; 
     m_value = newValue; 
     if (changed) 
     { 
      OnChange(this); 
     } 
    } 
    uint32_t Get() const 
    { 
     return m_value; 
    } 
}; 

class CSomeObserver 
    : public IObserver <CObservableUint32> 
{ 

public: 
    CSomeObserver() 
     : IObserver<CObservableUint32>() 
    { 

    } 
    virtual ~CSomeObserver() 
    { 

    } 
    virtual void OnChange(const CObservableUint32* what) 
    { 

    } 
}; 

Irgendwo anders ...

CObservableUint32 observable; 
    { 
     std::shared_ptr<IObserver<CObservableUint32> > observer = std::make_shared<CSomeObserver>(); 
     observable.RegisterObserver(observer); 
     observable.Set(42UL); 
    } 
    observable.Set(100); 
+0

Ich habe den wiederverwendbaren Teil zur Klasse CObservable ausgeklammert.Dies ermöglicht es den Benutzern, das hier gezeigte Muster wiederzuverwenden. Bitte bearbeite das nicht. – BitTickler