为什么 std::move of std::shared_ptr 会造成破坏
Why would a std::move of std::shared_ptr casue destruction
我有一些代码看起来像这样
struct A
{
int i;
A(int i) : i(i) {}
~A()
{
cout << "destroy " << i << endl;
}
};
using p = shared_ptr<A>;
p f(int i)
{
return make_shared<A>(i);
}
int main()
{
auto i = f(1);
cout << "a" << endl;
p && j = f(2);
cout << "a" << endl;
p && k = move(f(3));
cout << "a" << endl;
auto l = move(f(4));
cout << "a" << endl;
p ptr = make_shared<A>(5);
auto && a = move(ptr);
cout << "a" << endl;
}
输出为
a
a
destroy 3
a
a
a
destroy 5
destroy 4
destroy 2
destroy 1
我不明白为什么 move
函数的 return 值指向右值引用会导致破坏。但是直接放到一个右值引用就可以了。
问题实际上是在 std::tuple
的 std::get<>
中发现的。我有一个函数 return 两个 shared_ptr 的元组并使用 std::get<> 访问元素。但是我发现 auto && i = get<0>(f())
会导致错误,而 auto i = get<0>(f())
不会。然后我发现 std::move
的情况类似但更简单
因为通过使用 std::move
,您已将 return 值转换为参考。而不是 const 引用,所以它是临时的。
因此不会发生任何复制,该行一结束,return 值就会被销毁。
让我们一一了解这些:
auto i = f(1); // (1)
这将创建一个用 f(1)
的 return 值初始化的局部变量 i
(不是引用)。还行吧。 i
的生命周期是直到块结束。
p && j = f(2); // (2)
这会使用对 f(2)
编辑的对象 return 的引用来初始化引用 j
,这会延长 return 编辑的值 [=] 的生命周期20=],所以这没问题。 j
的生命周期是直到块结束。
p && k = move(f(3)); // (3)
这会使用对 move
编辑的对象 return 的引用来初始化引用,不会延长生命周期,因为 move
编辑的值 return 是一个引用由f(2)
编辑的临时return(而不是临时对象),但是由f(2)
编辑的临时return一旦k
被初始化,因为它的生命周期没有延长(它没有分配给引用变量),并且 k
最终成为一个悬空引用。
auto l = move(f(4)); // (4)
同(1),此招无效。 l
的生命周期是直到块结束。
p ptr = make_shared<A>(5); // (5)
这是局部变量初始化。 ptr
的生命周期是直到块结束。
auto && a = move(ptr);
a
是对 ptr
的引用。这不会改变 ptr
.
的生命周期
p && j = f(2);
这里,f
return 是 std::shared_ptr<A>
类型的纯右值。当您将引用绑定到纯右值时,它会将纯右值的生命周期延长为引用的生命周期。这意味着由 f
编辑的对象 return 将具有与 j
.
相同的生命周期
p && k = move(f(3));
在这里,再次 f
return 是一个纯右值。 std::move
的参数绑定到该纯右值,将其生命周期延长至参数 的生命周期 。 std::move
不是 return 纯右值。它 return 是一个 xvalue。生命周期扩展不适用于 xvalues,因此 f
编辑的对象 return 在 std::move
的参数生命周期结束后立即被销毁。也就是说,它在 std::move
return 时被销毁。 k
然后成为悬空引用。
我有一些代码看起来像这样
struct A
{
int i;
A(int i) : i(i) {}
~A()
{
cout << "destroy " << i << endl;
}
};
using p = shared_ptr<A>;
p f(int i)
{
return make_shared<A>(i);
}
int main()
{
auto i = f(1);
cout << "a" << endl;
p && j = f(2);
cout << "a" << endl;
p && k = move(f(3));
cout << "a" << endl;
auto l = move(f(4));
cout << "a" << endl;
p ptr = make_shared<A>(5);
auto && a = move(ptr);
cout << "a" << endl;
}
输出为
a
a
destroy 3
a
a
a
destroy 5
destroy 4
destroy 2
destroy 1
我不明白为什么 move
函数的 return 值指向右值引用会导致破坏。但是直接放到一个右值引用就可以了。
问题实际上是在 std::tuple
的 std::get<>
中发现的。我有一个函数 return 两个 shared_ptr 的元组并使用 std::get<> 访问元素。但是我发现 auto && i = get<0>(f())
会导致错误,而 auto i = get<0>(f())
不会。然后我发现 std::move
因为通过使用 std::move
,您已将 return 值转换为参考。而不是 const 引用,所以它是临时的。
因此不会发生任何复制,该行一结束,return 值就会被销毁。
让我们一一了解这些:
auto i = f(1); // (1)
这将创建一个用 f(1)
的 return 值初始化的局部变量 i
(不是引用)。还行吧。 i
的生命周期是直到块结束。
p && j = f(2); // (2)
这会使用对 f(2)
编辑的对象 return 的引用来初始化引用 j
,这会延长 return 编辑的值 [=] 的生命周期20=],所以这没问题。 j
的生命周期是直到块结束。
p && k = move(f(3)); // (3)
这会使用对 move
编辑的对象 return 的引用来初始化引用,不会延长生命周期,因为 move
编辑的值 return 是一个引用由f(2)
编辑的临时return(而不是临时对象),但是由f(2)
编辑的临时return一旦k
被初始化,因为它的生命周期没有延长(它没有分配给引用变量),并且 k
最终成为一个悬空引用。
auto l = move(f(4)); // (4)
同(1),此招无效。 l
的生命周期是直到块结束。
p ptr = make_shared<A>(5); // (5)
这是局部变量初始化。 ptr
的生命周期是直到块结束。
auto && a = move(ptr);
a
是对 ptr
的引用。这不会改变 ptr
.
p && j = f(2);
这里,f
return 是 std::shared_ptr<A>
类型的纯右值。当您将引用绑定到纯右值时,它会将纯右值的生命周期延长为引用的生命周期。这意味着由 f
编辑的对象 return 将具有与 j
.
p && k = move(f(3));
在这里,再次 f
return 是一个纯右值。 std::move
的参数绑定到该纯右值,将其生命周期延长至参数 的生命周期 。 std::move
不是 return 纯右值。它 return 是一个 xvalue。生命周期扩展不适用于 xvalues,因此 f
编辑的对象 return 在 std::move
的参数生命周期结束后立即被销毁。也就是说,它在 std::move
return 时被销毁。 k
然后成为悬空引用。