Pra*_*ian 328 c++ unique-ptr c++11
unique_ptr<T>
不允许复制构造,而是支持移动语义.然而,我可以unique_ptr<T>
从函数返回一个并将返回的值赋给变量.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码按预期编译和工作.那么该行如何1
不调用复制构造函数并导致编译器错误呢?如果我必须使用line 2
而不是它有意义(使用line 2
也可以,但我们不需要这样做).
我知道C++ 0x允许此异常,unique_ptr
因为返回值是一个临时对象,一旦函数退出就会被销毁,从而保证返回指针的唯一性.我很好奇这是如何实现的,它是在编译器中特殊的,还是在语言规范中有一些其他条款可以利用?
fre*_*low 204
这个漏洞是否在语言规范中有其他一些条款?
是的,见12.8§34和§35:
当满足某些条件时,允许实现省略类对象的复制/移动构造[...]复制/移动操作的省略,称为复制省略,允许在...中的返回语句中.具有类返回类型的函数,当表达式是非易失性自动对象的名称时,具有与函数返回类型相同的cv-nonqualified类型[...]
当满足复制操作的省略标准并且要通过左值指定要复制的对象时,首先执行用于选择复制的构造函数的重载决策,就好像该对象由右值指定一样.
只是想再增加一点,按值返回应该是默认选择,因为在最坏的情况下,返回语句中的命名值,即C++ 11,C++ 14和C++ 17中没有elisions的处理作为一个左值.因此,例如,以下函数使用-fno-elide-constructors
标志进行编译
std::unique_ptr<int> get_unique() {
auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
return ptr; // <- 2, moved into the to be returned unique_ptr
}
...
auto int_uptr = get_unique(); // <- 3
Run Code Online (Sandbox Code Playgroud)
在编译时设置标志,在此函数中发生两个移动(1和2),然后稍后移动(3).
Nik*_*nić 98
这绝不具体std::unique_ptr
,但适用于任何可移动的类.由于您按价值返回,因此语言规则保证了这一点.编译器尝试删除副本,如果无法删除副本则调用移动构造函数,如果无法移动则调用复制构造函数,如果无法复制则无法编译.
如果你有一个接受std::unique_ptr
作为参数的函数,你将无法将p传递给它.您必须显式调用move构造函数,但在这种情况下,您不应在调用后使用变量p bar()
.
void bar(std::unique_ptr<int> p)
{
// ...
}
int main()
{
unique_ptr<int> p = foo();
bar(p); // error, can't implicitly invoke move constructor on lvalue
bar(std::move(p)); // OK but don't use p afterwards
return 0;
}
Run Code Online (Sandbox Code Playgroud)
Bar*_*ski 37
unique_ptr没有传统的复制构造函数.相反,它有一个使用右值引用的"移动构造函数":
unique_ptr::unique_ptr(unique_ptr && src);
Run Code Online (Sandbox Code Playgroud)
右值参考(双&符号)仅绑定到右值.这就是当您尝试将左值unique_ptr传递给函数时出现错误的原因.另一方面,从函数返回的值被视为右值,因此自动调用移动构造函数.
顺便说一句,这将正常工作:
bar(unique_ptr<int>(new int(44));
Run Code Online (Sandbox Code Playgroud)
这里的临时unique_ptr是一个右值.
Dav*_*Lee 12
我认为在Scott Meyers的Effective Modern C++第25项中对它进行了完美的解释.这是一段摘录:
标准祝福RVO的部分接着说,如果满足RVO的条件,但编译器选择不执行复制省略,则返回的对象必须被视为右值.实际上,标准要求在允许RVO时,要么发生复制省略,要么
std::move
隐式应用于返回的本地对象.
这里,RVO指的是返回值优化,如果满足RVO的条件,则意味着返回在函数内声明的本地对象,你希望做RVO,这在他的书的第25项中也很好地解释了标准(这里本地对象包括return语句创建的临时对象).摘录的最大内容是复制省略或者std::move
隐式应用于返回的本地对象.Scott在第25项中提到,std::move
当编译器选择不删除副本并且程序员不应该明确地这样做时,会隐式应用.
在您的情况下,代码显然是RVO的候选者,因为它返回本地对象p
并且类型p
与返回类型相同,这导致复制省略.如果编译器选择不删除副本,无论出于何种原因,std::move
都会开始排队1
.
Vic*_*pta 10
我想提一下一种情况,您必须使用 std::move() 否则会出错。情况:如果函数的返回类型与局部变量的类型不同。
class Base { ... };
class Derived : public Base { ... };
...
std::unique_ptr<Base> Foo() {
std::unique_ptr<Derived> derived(new Derived());
return std::move(derived); //std::move() must
}
Run Code Online (Sandbox Code Playgroud)
参考: https: //www.chromium.org/developers/smart-pointer-guidelines
我在其他答案中没有看到的一件事是澄清另一个答案,即返回在函数中创建的 std::unique_ptr 与返回给该函数的 std::unique_ptr 之间存在差异。
这个例子可能是这样的:
class Test
{int i;};
std::unique_ptr<Test> foo1()
{
std::unique_ptr<Test> res(new Test);
return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
// return t; // this will produce an error!
return std::move(t);
}
//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
142205 次 |
最近记录: |