从unique_ptr创建shared_ptr

时间:2013-09-19 08:34:06

标签: c++ c++11 g++ smart-pointers clang++

在我最近审核的一段代码中,g++-4.6编译得很好,我遇到了从std::shared_ptr创建std::unique_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给出了编译错误
  • clang ++ - 3.2.1编译没有任何错误

所以问题是:哪个编译器在标准方面是正确的?标准是否要求这是一个无效的陈述,一个有效的陈述,还是仅仅是未定义?

加成

我们已经同意这些编译器中的一些,例如clang ++和g ++ - 4.6.4,允许转换,而不应该。但是对于g ++ - 4.7.3(在std::make_shared<Foo>(std::move(foo));上产生内部编译器错误),正确拒绝int bar(std::move(foo));

由于行为的这种巨大差异,我将问题保持不变,尽管其中的一部分可以通过减少到int bar(std::move(foo));来回答。


*)NUC:不是普遍可编辑的

3 个答案:

答案 0 :(得分:32)

更新2:bug已在r191150中的Clang中修复。 GCC使用正确的错误消息拒绝代码。


更新:我已提交了bug report。我的机器上的以下代码包含clang ++ 3.4(trunk 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尚未移出。标准保证在移动之后应该为null。 shared_ptr指向错误的值。

奇怪的是,它在没有警告的情况下编译,而valgrind不报告任何问题,没有泄漏,没有堆损坏。怪异。

如果我创建s_ptrshared_ptr ctor将右值引用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在标准要求移动后为空,s_ptr指向正确的值。这是正确的行为。


(原始答案。)

As Simple has pointed out:“除非Foo有一个带std :: unique_ptr的构造函数,否则它不应该编译。”

稍微扩展一下:make_shared将其参数转发给T的构造函数。如果T没有任何可以接受unique_ptr<T>&&的ctor,那就是编译错误。

但是,很容易修复此代码并获得您想要的内容(online demo):

#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) at the ctors of shared_ptr

答案 1 :(得分:11)

这不应该编译。如果我们暂时忽略指针的唯一性和共享性,它基本上是试图这样做:

int *u = new int;
int *s = new int(std::move(u));

这意味着动态创建int并使用对std::unique_ptr<int>的右值引用进行初始化。对于int,这根本就不应该编译。

对于一般课程Foo,它取决于课程。如果它有一个构造函数按std::unique_ptr<Foo>的值,const ref或rvalue ref,它将起作用(但可能不会做作者的意图)。在其他情况下,它不应该编译。

答案 2 :(得分:5)

这是一个简化编译错误的示例:

struct ptr
{
  int* p;

  explicit operator bool() const { return p != nullptr; }
};

int main()
{
  ptr u{};
  int* p = new int(u);
}

Clang使用显式bool转换运算符初始化int(英特尔编译器也会这样做。)

Clang 3.4不允许:

int i = int(u);

但确实允许:

int* p = new int(u);

我认为两者都应该被拒绝。 (Clang 3.3和ICC允许两者。)

我已将此示例添加到bug report