从函数返回 unique_ptr

新手上路,请多包涵

unique_ptr<T> 不允许复制构造,而是支持移动语义。然而,我可以从一个函数返回一个 unique_ptr<T> 并将返回的值分配给一个变量。

 #include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> foo()
{
  unique_ptr<int> p( new int(10) );

  return p;                   // 1
  //return move( p );         // 2
}

int main()
{
  unique_ptr<int> p = foo();

  cout << *p << endl;
  return 0;
}

上面的代码按预期编译和工作。那么 1 不调用复制构造函数并导致编译器错误是怎么回事?如果我不得不使用 2 行,那就有意义(使用 2 行也可以,但我们不需要这样做)。

我知道 C++0x 允许 unique_ptr 这个异常,因为返回值是一个临时对象,一旦函数退出就会被销毁,从而保证返回指针的唯一性。我很好奇这是如何实现的,它在编译器中是特殊情况还是语言规范中是否有其他条款可以利用?

原文由 Praetorian 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.7k
2 个回答

语言规范中是否还有其他条款可以利用?

是的,参见 12.8 §34 和 §35:

当满足某些条件时,允许实现省略类对象的复制/移动构造 […] 这种复制/移动操作的省略,称为 复制 省略,在返回语句中被允许 […]具有类返回类型的函数, 当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象的名称[…]

当满足复制操作的省略标准并且要复制的对象由左值指定时,首先执行 为复制选择构造函数的重载决策,就好像对象由右值指定一样


只是想再补充一点,按值返回应该是这里的默认选择,因为在最坏的情况下,return 语句中的命名值,即在 C++11、C++14 和 C++17 中没有省略被处理作为右值。例如,以下函数使用 -fno-elide-constructors 标志编译

std::unique_ptr<int> get_unique() {
  auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
  return ptr; // <- 2, moved into the to be returned unique_ptr
}

...

auto int_uptr = get_unique(); // <- 3

在编译时设置了标志,此函数中发生了两次移动(1 和 2),然后在(3)中发生了一次移动。

原文由 fredoverflow 发布,翻译遵循 CC BY-SA 3.0 许可协议

我认为在 Scott Meyers 的 Effective Modern C++ 的第 25 项 中完美地解释了这一点。这是一段摘录:

标准支持 RVO 的部分继续说,如果满足 RVO 的条件,但编译器选择不执行复制省略,则返回的对象必须被视为右值。实际上,标准要求当允许 RVO 时,复制省略发生或 std::move 隐式应用于返回的本地对象。

在这里, RVO 指的是 _返回值优化_, 如果满足 RVO 的条件,则 意味着返回您希望执行 RVO 的函数内部声明的本地对象,这在他的书的第 25 项中也有很好的解释,参考标准(这里的 本地对象 包括由 return 语句创建的临时对象)。摘录中最大的 _收获是发生复制省略或 std::move 隐式应用于返回的本地对象_。 Scott 在第 25 项中提到 std::move 当编译器选择不删除副本并且程序员不应该明确这样做时,会隐式应用。

在您的情况下,代码显然是 RVO 的候选者,因为它返回本地对象 p 并且 p 的类型与返回类型相同,这会导致复制省略。如果编译器选择不删除副本,无论出于何种原因, std::move 都会进入 1 行。

原文由 David Lee 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题