56

在我最近查看的一段代码中,它编译得很好g++-4.6,我遇到了一个奇怪的尝试来创建一个std::shared_ptrfrom 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:不可普遍编译

4

3 回答 3

33

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


更新:我已经提交了一个错误报告。以下代码在我的机器上使用 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并没有被移走。该标准保证它在被移出后应该为空。shared_ptr指向错误的值。

奇怪的是它编译时没有警告,valgrind 没有报告任何问题,没有泄漏,没有堆损坏。诡异的。

如果我s_ptr使用shared_ptrctor 将右值 ref 设置为 aunique_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指向正确的值。这是正确的行为。


(原来的答案。)

正如 Simple 所指出的那样:“除非 Foo 有一个使用 std::unique_ptr 的构造函数,否则它不应该编译。”

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

但是,很容易修复此代码并获得您想要的(在线演示):

#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有一个接受 的 ctor unique_ptr<Y,Deleter>&&,请参阅的 ctor处的 (13)shared_ptr

于 2013-09-19T14:35:24.817 回答
12

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

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

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

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

于 2013-09-19T08:41:52.653 回答
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 都允许。)

我已将此示例添加到错误报告中。

于 2013-09-19T22:15:21.043 回答