【问题标题】:Returning local unique_ptr as a shared_ptr将本地 unique_ptr 作为 shared_ptr 返回
【发布时间】:2016-06-24 16:39:35
【问题描述】:

我习惯于在返回 std::unique_ptr 时不使用 std::move,因为这样做会禁止 RVO。我有这种情况,我有一个本地std::unique_ptr,但返回类型是std::shared_ptr。 以下是代码示例:

shared_ptr<int> getInt1() {
    auto i = make_unique<int>();

    *i = 1;

    return i;
}

shared_ptr<int> getInt2() {
    return make_unique<int>(2);
}

unique_ptr<int> getInt3() {
    auto ptr = make_unique<int>(2);

    return ptr;
}

int main() {
    cout << *getInt1() << endl << *getInt2() << *getInt3() << endl;
    return 0;
}

GCC 接受这两种情况,但 Clang 拒绝 getInt1() 并出现此错误:

main.cpp:10:13: error: no viable conversion from 'std::unique_ptr<int, std::default_delete<int> >' to 'shared_ptr<int>'
    return i;
           ^

以下是有关 coliru 的两种情况:GCCClang

两个编译器都接受第三种情况。

哪一个是错的?谢谢。

【问题讨论】:

  • 我怀疑,当我们了解您的需要时,我们将能够提供更好的答案。目前,这可以通过调用 make_shared 而不是 make_unique 轻松解决。我认为这是不可能的,这将有助于理解这个原因。
  • @Elliott:有什么可以解决的?他不是在问如何解决任何问题,而是在问他的代码是否符合标准。

标签: c++ gcc clang language-lawyer c++14


【解决方案1】:

正确答案取决于您所谈论的 C++ 标准。

如果我们谈论的是 C++11,clang 是正确的(需要明确的移动)。如果我们谈论的是 C++14,那么 gcc 是正确的(不需要显式移动)。

C++11 在 N3290/[class.copy]/p32 中说:

当满足或将满足省略复制操作的条件时 遇到源对象是函数参数这一事实, 并且要复制的对象由左值指定,重载 首先执行为副本选择构造函数的分辨率 好像对象是由右值指定的。如果重载决议 失败,...

这要求您仅在返回表达式与函数返回类型具有相同类型时才获得隐式移动。

但是CWG 1579 改变了这一点,这个缺陷报告在 C++11 之后被接受,并且及时用于 C++14。同一段现在改为:

当满足删除复制/移动操作的条件时,但 不适用于异常声明,而要复制的对象是 由左值指定,或者当return 语句中的表达式 是一个(可能带括号的)id-expression,它用 在正文中声明的自动存储持续时间或 parameter-declaration-clause 最里面的封闭函数或 lambda-expression,为选择构造函数的重载决议 首先执行复制,就好像对象是由 右值。如果第一次重载决议失败或没有执行,...

这个修改基本上允许返回表达式类型是convertible-to函数返回类型并且仍然有资格进行隐式移动。

这是否意味着代码需要基于__cplusplus的值的#if/#else

可以这样做,但我不会打扰。如果我的目标是 C++14,我会:

return i;

如果代码在 C++11 编译器下意外运行,您将在编译时收到错误通知,并且修复起来很简单:

return std::move(i);

如果您只是针对 C++11,请使用 move

如果您想同时针对 C++11 和 C++14(及更高版本),请使用 move。无偿使用move 的缺点是可以禁止RVO(返回值优化)。但是,在这种情况下,RVO 甚至是不合法的(因为从 return 语句转换为函数的返回类型)。所以无偿的move 不会伤害任何东西。

有一次你可能会倾向于无偿使用move,即使是针对 C++14,如果没有它,仍然可以在 C++11 中编译,并调用昂贵的 copy 转换,而不是 move 转换。在这种情况下,意外在 C++11 下编译会引入一个无声的性能错误。而且当在 C++14 下编译时,无偿的move 仍然没有任何不利影响。

【讨论】:

  • 如果您只使用 C++11,我会鼓励在返回之前创建正确的类型,而不是在代码中使用“return std::move( ...”。因为那样编译器可能只需做 NRVO 而不是移动几次。
【解决方案2】:

std::unique_ptr 仅当它是右值时才能用于构造std::shared_ptr。见std::shared_ptr的构造函数声明:

template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r );

所以你需要使用std::move 来使第一种情况起作用,否则它应该会失败。

return std::move(i);

顺便说一句:我用gcc 4.9.3 编译代码也失败了。

source_file.cpp:14:12: error: cannot bind ‘std::unique_ptr<int, std::default_delete<int> >’ 
lvalue to ‘std::unique_ptr<int, std::default_delete<int> >&&’
     return i;
            ^

【讨论】:

  • 您好,您能否更改您的答案以指定仅c++11 需要移动?
猜你喜欢
  • 1970-01-01
  • 2015-10-17
  • 1970-01-01
  • 2017-11-09
  • 2014-11-12
  • 2021-01-17
  • 1970-01-01
  • 2016-06-05
  • 1970-01-01
相关资源
最近更新 更多