将 unique_ptr<Object> 作为 unique_ptr<const Object> 返回

5

I have a method like this:

std::unique_ptr<const Stats> Table::GetStats() const {
  std::unique_ptr<Stats> result;
  // ...
  // Prepare stats. Under some conditions an exception may be thrown.
  // ...
  return result;
}

问题在于它无法编译:

错误:无法将“std::unique_ptr”左值绑定到“std::unique_ptr&&”

我可以通过以下方法绕过使其编译:
return std::unique_ptr<const Stats>(result.release());

但这似乎有点过度了。我不明白,在C ++的角度看,第一段代码有什么问题吗?是否有更优雅的解决方案?


@RichardHodges,gcc(Debian 4.9.2-10+deb8u1)4.9.2 - Alexey
@RichardHodges 可能您会在没有 OP 的情况下得到 RVO,// 准备统计数据。某些情况下可能会抛出异常。 - Alexey Usachov
@AlexeyUsachov,"OP"是什么意思? - Alexey
1
@Alexey,那个太老了。它会有错误。你可以尝试使用 return { std::move(result) }; - Richard Hodges
1
@RichardHodges 原帖发布者,帖子所有者 - Alexey Usachov
显示剩余2条评论
1个回答

4
您的代码应该能够正常工作,因为在返回语句中:

(强调我的)

(自C++11以来)

如果表达式是一个左值表达式,并且满足复制省略的条件,或者将满足这些条件,但表达式命名了函数参数,则执行两次选择构造函数来初始化返回值的重载分辨率: 首先,就好像表达式是右值表达式一样(因此它可能选择移动构造函数或接受const引用的复制构造函数), 如果没有合适的转换可用,则第二次执行重载分辨率,使用左值表达式(因此它可能选择带非const引用的复制构造函数)。

即使函数返回类型与表达式类型不同(复制省略需要相同类型),上述规则也适用。

这意味着,即使result是一个左值,它也会被视为右值,然后将选择以下构造函数,它可以将std::unique_ptr<Stats>转换为std::unique_ptr<const Stats>

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;

看起来gcc4.9.2不会这样处理(即首先将表达式视为rvalue表达式);gcc 9可以正常工作。

正如@RichardHodges评论的那样,你可以使用std::move作为解决方法


编译器在C++17之前不需要进行任何复制省略,因此它的行为是正确的。std::move确实是解决问题的方法。 - Pezo
@Pezo 只需要满足“复制省略的条件……”,而不是实际应用复制省略。 - spkersten
标准中是否有复制省略的条件?它是否被定义但不是必需的? - Pezo
@Pezo 我不确定我完全理解你的问题。请参阅 https://en.cppreference.com/w/cpp/language/copy_elision,了解编译器允许执行复制省略的条件。正如你所说,从C++17开始是强制性的。 - spkersten
我明白了,有些情况下是允许的,因为它可以改变可观察的行为。我一直以为这是按照“好像”的方式考虑的,那么它总是被允许的。 - Pezo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接