我正在更新一些旧的代码,这些代码使用auto_ptr
来代替unique_ptr
。这主要是一项搜索和替换工作,但我发现我在某个代码返回unique_ptr
的地方收到编译错误。
以下是说明问题的示例:
#include <string>
#include <iostream>
#include <memory>
#include <utility>
using namespace std;
struct Foo {
unique_ptr<string> us;
Foo() {
this->us = unique_ptr<string>(new string("hello"));
}
unique_ptr<string> bar() {
return this->us;
}
};
int main(int argc, const char **argv) {
Foo foo;
unique_ptr<string> s = foo.bar();
cout << *s << endl;
}
当我编译它时,我得到了:
t1.cpp: In member function ‘std::unique_ptr<std::basic_string<char> > Foo::bar()’:
t1.cpp:17:16: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = std::basic_string<char>; _Dp = std::default_delete<std::basic_string<char> >]’
return this->us;
^~
In file included from /opt/rh/devtoolset-7/root/usr/include/c++/7/memory:80:0,
from t1.cpp:4:
/opt/rh/devtoolset-7/root/usr/include/c++/7/bits/unique_ptr.h:388:7: note: declared here
unique_ptr(const unique_ptr&) = delete;
^~~~~~~~~~
如果我更改错误行以指定这样的举动:
return move(this->us);
然后它起作用。
我发现有很多参考资料表明不需要搬家-例如this SO question和来自铬项目的these guidelines。
我的问题是:为什么在这种情况下需要明确指定移动?我以某种方式返回实例变量的值与事实有关吗?
如果这是一个骗子,请提前道歉-我敢肯定会问过这个问题,但是我很难找到能找到它的搜索词。
答案 0 :(得分:5)
所有与语义有关。 unique_ptr
是唯一所有权,因此它将被返回或以us
的形式返回,但不能同时返回。该标准的规则幸运地保护您免受这种误解。
您可以做的是返回一个临时unique_ptr
(将被移动):
unique_ptr<string> bar() {
return make_unique<string>("hello, world");
}
但是,如果您需要在多个地方复制unique_ptr
,main()
似乎就是这种情况,那么您并不是在寻找唯一的所有权,而是共享所有权< / strong>。因此,您应该转到shared_ptr
:
struct Foo {
shared_ptr<string> us;
Foo() {
us = make_shared<string>("hello, world");
}
shared_ptr<string> bar() {
return us;
}
};
int main(int argc, const char **argv) {
Foo foo;
auto s = foo.bar();
cout << *s << endl;
}
答案 1 :(得分:4)
您找到的所有引用都提到返回的对象必须是函数局部变量。您返回一个成员。返回时,成员变量不会被隐式视为右值。
并非没有道理。除非明确地完成,否则一个类通常不希望失去其资源的所有权。
当函数返回时,局部函数将要死亡,因此让编译器隐式地蚕食它是一个值得优化的优化方法。
答案 2 :(得分:3)
您正尝试返回Foo::us
类型的std::unique_ptr<>
成员变量。 std::unique_ptr<>
的唯一目的是不可复制但可移动。这就是为什么返回需要std::move
才能将Foo::us
成员变量重置为null
的原因。
bar
函数可能应该通过值或const引用返回该std::string
:
string const& bar() const {
return *this->us;
}
然后:
int main() {
Foo foo;
string const& s = foo.bar();
cout << s << endl;
}
答案 3 :(得分:2)
std::auto_ptr
的正确使用与std::unique_ptr
的用途相同,它拥有对单个对象的唯一引用,该引用在指针被破坏时被删除。
std::auto_ptr
早于移动语义,因此它具有一个复制构造函数,其行为类似于移动构造函数。意外地复制std::auto_ptr
却不知道原来的std::auto_ptr
现在是空的,很容易。
由于std::unqiue_ptr
仅可移动而不能复制,因此可以避免这种情况。您必须使用std::move
明确放弃对指针的所有权。
例外情况是为本地堆栈分配了std::unique_ptr
,无论如何它们都将在当前作用域的末尾销毁,因此该语言允许您不使用{{1}而返回std::unique_ptr
}。由于您的std::move
变量不是局部变量,因此不允许直接将其返回,以防止意外释放所有权。
答案 4 :(得分:1)
这显示了auto_ptr的确切危险以及为何将其删除。
如果您想两次调用该函数,则可以看到问题所在。
auto_ptr<string> Foo::bar() {
return this->us;
}
int func() {
Foo foo;
auto_ptr<string> a = foo.bar();
auto_ptr<string> b = foo.bar();//uh oh, null ptr
}
如果您喜欢这种行为,则可以使用unique_ptr轻松模拟它。
#include <string>
#include <iostream>
#include <memory>
#include <utility>
using namespace std;
struct Foo {
unique_ptr<string> us;
Foo() {
this->us = unique_ptr<string>(new string("hello"));
}
unique_ptr<string> bar() {
unique_ptr<string> local_us(us.release());
return local_us;
}
};
int main(int argc, const char **argv) {
Foo foo;
unique_ptr<string> a = foo.bar();
unique_ptr<string> b = foo.bar();//b is a nullptr
cout << *s << endl;
}