为什么unique_ptr为空

Why is unique_ptr null?

本文关键字:为空 ptr unique 为什么      更新时间:2023-10-16

在下面的代码片段中,foo中的断言总是触发。

谁能解释为什么ynullptr?这看起来像一个终身问题,即 yputget的调用之间被摧毁,但我真的不明白为什么。

我错过了什么?

蒂亚

class Y
{
public:
    Y(const std::string& name) : m_name(name)
    {
    }
    const std::string& getName() const
    {
        return m_name;
    }
private:
    Y(const Y&);
    const std::string m_name;
};
void foo(std::unique_ptr<Y> y)
{
    if (y == nullptr)
    {
        // ALWAYS FIRES
        assert(false && "nullptrn");
    }
    std::string name = y->getName();
    std::cout << "name: " << name << "n";
}
class X
{
public:
    X() {}
    void put(std::unique_ptr<Y> y)
    {
        m_queue.push([&] { foo(std::move(y)); });
    }
    void get()
    {
        std::function<void()> func = m_queue.front();
        func();
    }
private:
    std::queue<std::function<void()>> m_queue;
};

int main()
{
    std::unique_ptr<Y> y(new Y("fred"));
    X x;
    x.put(std::move(y));
    x.get();
    return 0;
}
void put(std::unique_ptr<Y> y)
{
    m_queue.push([&] { foo(std::move(y)); });
}

在此函数中,y是一个局部变量,当它超出范围时会被销毁。局部变量被 lambda 捕获(通过引用),在执行时不存在 — 它指向什么都没有/null/垃圾/任何东西,因为y已经被销毁了。

你的问题是双重的。 首先,您通过引用捕获其生存期(及其副本的生存期)超过当前本地范围的 lambda。 别这样。 仅当您的 lambda(和所有副本)不会复制到本地范围的生存期之外时,才使用 [&]

天真的答案是然后做一个[=][y],但你不能复制一个唯一的指针。

在 C++14 中,您可以执行将y移动到 lambda 捕获中的[y=std::move(y)]。 但是,无法复制捕获unique_ptr按值的 lambda。 std::function只能存储可调用、可销毁和可复制的对象。

解决这个问题的一个解决方案是等待一个只移动function(我认为它正在下降 - 我至少看到了一个非正式的提案),或者自己提出。

template<class Sig>
struct unique_function;
namespace details {
  template<class Sig>
  struct i_uf_impl;
  template<class R, class...Args>
  struct i_uf_impl<R(Args...)> {
    virtual ~i_uf_impl() {}
    virtual R invoke(Args&&...) = 0;
  };
  template<class Sig, class F>
  struct uf_impl;
  template<class R, class...Args>
  struct uf_impl<R(Args...):i_uf_impl<R(Args...)>{
    F f;
    virtual R invoke(Args&&...args) override final {
      return f(std::forward<Args>(args)...);
    }
  };
  template<class...Args>
  struct uf_impl<void(Args...):i_uf_impl<void(Args...)>{
    F f;
    virtual void invoke(Args&&...args) override final {
      f(std::forward<Args>(args)...);
    }
  };
}
template<class R, class...Args>
struct unique_function<R(Args...)> {
  std::unique_ptr<details::i_uf_impl<R(Args...)>> pimpl;
  unique_function(unique_function&&)=default;
  unique_function& operator=(unique_function&&)=default;
  unique_function()=default;
  template<class F, class=std::enable_if_t<
    !std::is_same<std::decay_t<F>, unique_function>
    && ( std::is_convertible<std::result_of_t< F(Args...) >, R >{}
      || std::is_same< R, void >{} )
  >>
  unique_function(F&& f):pimpl(
    new details::uf_impl<R(Args...), std::decay_t<F>>{std::forward<F>(f)}
  ) {}
  // override deduction helper:
  unique_function(R(*pfun)(Args...)):pimpl(
    pfun?
      new details::uf_impl<R(Args...), R(*)(Args...)>{pfun}
    : nullptr
  ) {}
  // null case
  unique_function(nullptr_t):unique_function(){}
  explicit bool operator() const { return static_cast<bool>(pimpl); }
  R operator()(Args...args)const{
    return pimpl->invoke( std::forward<Args>(args)... );
  }
};

这可能有效,也可能无效,但应该给你要点。

void put(std::unique_ptr<Y> y)
{
    m_queue.push([&] { foo(std::move(y)); });
}

在这里,您推送一个包含对局部变量 y 的引用的 lambda 。当你离开put的那一刻,局部变量被销毁,lambda包含一个悬空的引用。任何进一步的行为都是未定义的。

您需要通过将局部变量移动到 lambda 中来捕获局部变量,但这非常先进,但也是不够的,因为std::function无法容纳仅移动函数对象。解决此问题的最简单方法是从 unique_ptr 切换到 shared_ptr 并按 lambda 中的值捕获。

因为y被摧毁了:

void put(std::unique_ptr<Y> y)
{
    m_queue.push([&] {  // <== capturing y by-reference
        foo(std::move(y)); 
    });
    // <== y gets destroyed here
}

put()结束时,y已经被清理干净了。

您希望函子拥有y的所有权,理想情况下如下所示:

[p = std::move(y)] {
    foo(std::move(p));
}

上面的lambda有一个类型为unique_ptr<Y>的成员变量,所以它的复制构造函数被隐式删除。但[func.wrap.func.con]规定:

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

要求FCopyConstructible

所以这也不会编译。 这让你有些卡住了。