【问题标题】:creating a shared_ptr from unique_ptr从 unique_ptr 创建 shared_ptr
【发布时间】:2013-09-19 08:34:05
【问题描述】:

在我最近查看的一段代码中,使用 g++-4.6 编译得很好,我遇到了一个奇怪的尝试,从 std::unique_ptr 创建一个 std::shared_ptr

std::unique_ptr<Foo> foo...
std::make_shared<Foo>(std::move(foo));

这对我来说似乎很奇怪。这应该是 std::shared_ptr&lt;Foo&gt;(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&lt;Foo&gt;(std::move(foo)); 上产生内部编译器错误),正确拒绝 int bar(std::move(foo));

由于行为上的巨大差异,我将问题保留原样,尽管部分问题可以通过减少到 int bar(std::move(foo)); 来回答。


*) NUC:不可普遍编译

【问题讨论】:

  • 除非Foo 的构造函数采用std::unique_ptr&lt;Foo&gt;,否则它不应该编译。
  • 当你的意思是“不编译”时,说“编译时出现正确的错误”是令人困惑的。问题标题令人困惑,您不是从 unique_ptr 创建 shared_ptr,而是从 unique_ptr 创建 int
  • @JonathanWakely 我调整了配方。但是,我将保持原样。为什么?开发人员的意图是这样创建std::shared_ptr&lt;Foo&gt;。从unique_ptr 创建一个int 不是这个问题的重点,而是为什么不同的编译器允许这个语句或者惨遭失败。
  • 仅供参考,我检查了unique_ptr 是否可以隐式转换为普通指针(但不是)
  • @Naszta 不要写std::shared_ptr&lt;int&gt; shptr(foo.release());!这是可能导致内存泄漏的危险代码。如果shared_ptr 未能分配其引用块并引发异常,则您现在已经泄漏了内存。

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


【解决方案1】:

更新 2: 这个 bug 已在 Clang 的 r191150 中修复。 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 尚未被移出。标准保证它在被移出后应该为空。 shared_ptr 指向错误的值。

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

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


(原来的答案。)

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

稍微扩展一下:make_shared 将其参数转发给 T 的构造函数。如果 T 没有任何可以接受 unique_ptr&lt;T&gt;&amp;&amp; 的 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&lt;Y,Deleter&gt;&amp;&amp; 的 ctor,请参阅 (13) at the ctors of shared_ptr

【讨论】:

  • @stefan 对不起,我误解了你的问题。我又花了 5 分钟才明白这里的真正问题。请检查我更新的答案。我将在今天晚些时候回复您使用最新的 clang 获得的结果。
  • 现在探索clang的不当行为是一个很好的答案。谢谢你!
  • 原来g++-4.6.4允许operator bool()的中间使用先将unique_ptr&lt;T&gt;转换成bool再转换成int。可能,clang++ 也是如此。
  • @stefan 感谢您的信息。请给点时间,我会检查最新的clang。在我看来,std::make_shared&lt;int&gt;(std::move(foo)); 行不应该编译。如果是这样,那就是一个错误。顺便问一下,你在哪里找到这些信息的?
  • @stefan 顺便说一下,这个问题 +1,一个非常奇怪的编译器错误。
【解决方案2】:

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

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

这意味着它动态地创建了一个int 并使用对std::unique_ptr&lt;int&gt; 的右值引用对其进行初始化。对于ints,根本不应该编译。

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

【讨论】:

  • 对,很好的答案。有趣的是,智能指针版本和您的版本之间仍然存在差异,因为所有提到的编译器都正确拒绝了您的示例代码。
【解决方案3】:

下面是一个 clang 错误编译的简化示例:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-10
    • 2016-06-05
    • 1970-01-01
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多