从unique_ptr创build一个shared_ptr
在我最近回顾的一段代码中,用g++-4.6
编译得很好,我遇到了一个奇怪的尝试,从std::unique_ptr
创build一个std::shared_ptr
:
std::unique_ptr<Foo> foo... std::make_shared<Foo>(std::move(foo));
这对我来说似乎很奇怪。 这应该是std::shared_ptr<Foo>(std::move(foo));
afaik,虽然我不完全熟悉的动作(我知道std::move
只是一个演员,没有得到移动)。
使用此SSC上的不同编译器进行检查(NUC *)E
#include <memory> int main() { std::unique_ptr<int> foo(new int); std::make_shared<int>(std::move(foo)); }
汇编结果:
- g ++ – 4.4.7给出编译错误
- g ++ – 4.6.4编译没有任何错误
- g ++ – 4.7.3给出了内部编译器错误
- g ++ – 4.8.1给出了编译错误
- 铿锵声++ – 3.2.1编译没有任何错误
所以问题是:哪个编译器在标准方面是正确的? 标准是否要求这是一个无效的陈述,一个有效的陈述,或者这只是未定义的?
加成
我们已经同意,其中一些编译器(如clang ++和g ++ – 4.6.4)允许转换,而不应该转换。 然而,使用g ++ – 4.7.3(它在std::make_shared<Foo>(std::move(foo));
)上产生内部编译器错误,正确地拒绝了int bar(std::move(foo));
由于这种巨大的行为差异,我将这个问题保持原样,尽pipe其中的一部分可以通过减lessint bar(std::move(foo));
来回答int bar(std::move(foo));
。
*)NUC:不是普遍可编辑的
更新2:这个bug已经在r191150的Clang中被修复了。 海湾合作委员会拒绝代码与一个适当的错误消息。
更新:我已经提交了一个错误报告 。 下面的代码在我的机器上与铿锵++ 3.4(中继191037)
#include <iostream> #include <memory> int main() { std::unique_ptr<int> u_ptr(new int(42)); std::cout << " u_ptr.get() = " << u_ptr.get() << std::endl; std::cout << "*u_ptr = " << *u_ptr << std::endl; auto s_ptr = std::make_shared<int>(std::move(u_ptr)); std::cout << "After move" << std::endl; std::cout << " u_ptr.get() = " << u_ptr.get() << std::endl; std::cout << "*u_ptr = " << *u_ptr << std::endl; std::cout << " s_ptr.get() = " << s_ptr.get() << std::endl; std::cout << "*s_ptr = " << *s_ptr << std::endl; }
打印这个:
u_ptr.get() = 0x16fa010 *u_ptr = 42 After move u_ptr.get() = 0x16fa010 *u_ptr = 42 s_ptr.get() = 0x16fa048 *s_ptr = 1
正如你所看到的, unique_ptr
还没有被移出。 该标准保证它在被移出后应该是空的。 shared_ptr
指向一个错误的值。
奇怪的是,它没有警告编译,valgrind不报告任何问题,没有泄漏,没有堆腐败。 奇怪的。
如果我使用shared_ptr
创builds_ptr
并将右值引用到unique_ptr
而不是make_shared
则会显示正确的行为:
#include <iostream> #include <memory> int main() { std::unique_ptr<int> u_ptr(new int(42)); std::cout << " u_ptr.get() = " << u_ptr.get() << std::endl; std::cout << "*u_ptr = " << *u_ptr << std::endl; std::shared_ptr<int> s_ptr{std::move(u_ptr)}; std::cout << "After move" << std::endl; std::cout << " u_ptr.get() = " << u_ptr.get() << std::endl; //std::cout << "*u_ptr = " << *u_ptr << std::endl; // <-- would give a segfault std::cout << " s_ptr.get() = " << s_ptr.get() << std::endl; std::cout << "*s_ptr = " << *s_ptr << std::endl; }
它打印:
u_ptr.get() = 0x5a06040 *u_ptr = 42 After move u_ptr.get() = 0 s_ptr.get() = 0x5a06040 *s_ptr = 42
正如你所看到的那样, u_ptr
在移动之后是按照标准所要求的u_ptr
是空的,并且s_ptr
指向正确的值。 这是正确的行为。
(原来的答案)
正如Simple所指出的那样 :“除非Foo有一个构造函数需要std :: unique_ptr,否则不应该编译。
稍微扩展一下: make_shared
将其参数转发给T的构造函数。 如果T没有任何可以接受的unique_ptr<T>&&
这是一个编译错误。
但是,修复这个代码很容易,并得到你想要的( 在线演示 ):
#include <memory> using namespace std; class widget { }; int main() { unique_ptr<widget> uptr{new widget}; shared_ptr<widget> sptr(std::move(uptr)); }
重点是: make_shared
在这种情况下使用是错误的。 shared_ptr
有一个接受unique_ptr<Y,Deleter>&&
的ctor,参见(13)在shared_ptr
。
这不应该编译。 如果我们暂时忽视指针的独特性和共享性,基本上就是这样做的:
int *u = new int; int *s = new int(std::move(u));
这意味着它dynamic地创build一个int
并用一个对std::unique_ptr<int>
的右值引用进行初始化。 对于int
s,那根本就不应该编译。
对于一般的class级来说,这取决于class级。 如果它有一个构造函数根据值,const ref或rvalue参考std::unique_ptr<Foo>
,它将工作(但也许不会做作者的意图)。 在其他情况下,它不应该编译。
这是clang错误编译的一个简化例子:
struct ptr { int* p; explicit operator bool() const { return p != nullptr; } }; int main() { ptr u{}; int* p = new int(u); }
Clang使用显式的bool转换运算符来初始化int
(英特尔编译器也是如此)。
铿锵3.4不允许:
int i = int(u);
但它确实允许:
int* p = new int(u);
我觉得两者都应该被拒绝。 (Clang 3.3和ICC允许)。
我已经将这个例子添加到错误报告 。