从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允许)。

我已经将这个例子添加到错误报告 。