为什么 unique_ptr 为空?
Why is unique_ptr null?
在下面的代码片段中,foo
中的断言总是会触发。
谁能解释为什么 y
是 nullptr
?这看起来像是一个终身问题,即 y
在调用 put
和 get
之间被破坏,但我真的不明白为什么。
我错过了什么?
TIA
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 && "nullptr\n");
}
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 捕获(通过引用),在执行时不存在——它指向 nothing/null/garbage/whatever,因为 y
已经被摧毁了。
因为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);
Requires: F
shall be CopyConstructible
.
所以这也不会编译。这让你有点卡住了。
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 中按值捕获。
您的问题有两个方面。首先,您在生命周期(及其副本的生命周期)超过当前本地范围的 lambda 中通过引用进行捕获。不要那样做。仅当您的 lambda(和所有副本)不会被复制到本地作用域的生命周期之外时才使用 [&]
。
天真的答案是然后做一个[=]
或[y]
,但你不能复制一个唯一的指针。
在 C++14 中,您可以执行 [y=std::move(y)]
将 y
移动到 lambda 捕获中。但是,无法复制已捕获 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)... );
}
};
这可能有效也可能无效,但应该给了你要点。
在下面的代码片段中,foo
中的断言总是会触发。
谁能解释为什么 y
是 nullptr
?这看起来像是一个终身问题,即 y
在调用 put
和 get
之间被破坏,但我真的不明白为什么。
我错过了什么?
TIA
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 && "nullptr\n");
}
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 捕获(通过引用),在执行时不存在——它指向 nothing/null/garbage/whatever,因为 y
已经被摧毁了。
因为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);
Requires:
F
shall beCopyConstructible
.
所以这也不会编译。这让你有点卡住了。
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 中按值捕获。
您的问题有两个方面。首先,您在生命周期(及其副本的生命周期)超过当前本地范围的 lambda 中通过引用进行捕获。不要那样做。仅当您的 lambda(和所有副本)不会被复制到本地作用域的生命周期之外时才使用 [&]
。
天真的答案是然后做一个[=]
或[y]
,但你不能复制一个唯一的指针。
在 C++14 中,您可以执行 [y=std::move(y)]
将 y
移动到 lambda 捕获中。但是,无法复制已捕获 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)... );
}
};
这可能有效也可能无效,但应该给了你要点。