将本地unique_ptr作为shared_ptr返回

时间:2016-03-10 02:03:32

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

我在返回std::move时过去常常不使用std::unique_ptr,因为这样做会禁止使用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

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

哪一个错了?感谢。

2 个答案:

答案 0 :(得分:17)

正确答案取决于您所谈论的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 ,用于选择构造函数的重载决策   首先执行复制,就好像对象是由a指定的一样   右值。如果第一个重载决策失败或未执行,...

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

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

有人可以这样做,但我不会打扰。如果我的目标是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中编译,并调用昂贵的副本转换,而不是移动转换。在这种情况下,在C ++ 11下意外编译会引入静默性能错误。当在C ++ 14下编译时,无偿的move仍然没有任何不利影响。

答案 1 :(得分:9)

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);

BTW:我用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;
            ^