【问题标题】:C++ weak_ptr.lock() segfaultC++ weak_ptr.lock() 段错误
【发布时间】:2015-02-06 04:20:45
【问题描述】:

所以我有这个函数来添加侦听器,它会转换类的共享指针,以便稍后在收到通知时调用它。

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;

由于某种原因,“iter->lock()”段错误。我会说这是一个 Qt 应用程序,但我故意没有创建任何线程(据我所知),所以我只是超级困惑我做错了什么以使这些weak_ptrs 中断。所以如果我在 gdb 中运行它,它工作得很好。但是如果我设置,“设置禁用随机化关闭”然后我得到错误。所以我觉得这是一个奇怪的问题,其中有未初始化的变量。如果有帮助,这是我在 gdb 中崩溃时的堆栈。

#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>) ()

编辑:Michael Burr 发布了在添加新听众的同时注册听众的可能性,这完全有可能发生。这将导致迭代器无效,并且当我在不是weak_ptr 的一段内存上调用weak_ptr.lock() 时,BOOM。我认为这里有一个寓意,我只需要找到它。

【问题讨论】:

  • 你能给我们一个完整的带有 main() 的最小工作程序吗?
  • 您可以将auto weak = std::weak_ptr&lt;T&gt;(listener); 简化为std::weak_ptr&lt;T&gt; weak(listener);。此外,您有一个名为notify() 的函数,它接受一个同名的参数 - 这也是一个函数!
  • 我可以稍后再尝试。实际上我还没有更简单的程序崩溃。没有意义的事情是,在过去的 6 个月里,这很有效,然后我添加了一个新的监听器,然后 BOOM 突然一切都爆炸了。但我什至甚至区分了新的听众和它完全一样!
  • @JohnZwinck 我完全承认我以前从未注意过这个名字。让我解决这个问题。
  • 标准现在强制您首先获取 shared_ptr,然后才能使用weak_ptr 的内容。过期版本的 boost 可能更糟,因为它可能会引入竞争条件。 if(!wp.expired) { wp-&gt;Use(); // race! }

标签: c++ qt weak-references


【解决方案1】:

notify() 被调用时,通过onNotify() 函数对象调用的函数是否可能会导致registerListener() 被间接调用(或其他一些可以在listeners 集合中添加或删除条目的代码) ?

如果是这样,那么在 notify() for 循环中使用的 iter 可能会失效。您可能希望将notify() 更改为如下所示,它将所有shared_ptr 对象排队以进行通知,以便在任何onNotify() 回调期间修改listeners 集合都无关紧要:

#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();
    }

}

【讨论】:

  • 这样的循环也可以通过确保事件监听器只获得指向可观察对象的常量指针以及 registerListener() 不是常量操作来防止。
  • 是的!这解决了它。我不知道为什么我以前没有看到。我又觉得自己像个白痴。我们在 Android 应用程序中也遇到了完全相同的问题。注意自我无效的迭代器真的很糟糕。我一直保持这个 QML 应用程序完全异步而不是多线程,所以我在通知听众时没有考虑修改列表。
  • @Buttink:这是一件很容易错过的事情,因为使迭代器无效的操作是看不见的。关于迭代器何时失效有一系列规则并没有帮助。无论如何,MSVC 在其调试版本中具有非常好的迭代器有效性跟踪,但有时它的配置可能会令人困惑(搜索 _ITERATOR_DEBUG_LEVEL_HAS_ITERATOR_DEBUGGING)。我不知道 GCC 的工具链中有任何类似的迭代器调试支持。
【解决方案2】:

我在您显示的代码中找不到明显的问题。所以我认为,问题在于您没有显示的代码。请记住,Q_OBJECT 装饰对象也有与 qt 相关的生命周期管理。也许有一些干扰......

如果您将您的代码与我在此处发布的代码进行比较,或许会有所帮助,您很快就能找到关键的区别。

#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)
    {

    }
};

别处...

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

【讨论】:

  • 我将可重用部分分解为 CObservable 类。这允许人们重复使用此处显示的模式。请不要修改它。
猜你喜欢
  • 1970-01-01
  • 2018-07-04
  • 2017-08-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-12
  • 1970-01-01
  • 1970-01-01
  • 2020-07-10
相关资源
最近更新 更多