将本地unique_ptr作为shared_ptr返回

Returning local unique_ptr as a shared_ptr

本文关键字:ptr shared 返回 作为 unique      更新时间:2023-10-16

我习惯于在返回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上的两种情况:GCC、Clang

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

哪一个错了?谢谢

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

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

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

当满足或将满足省略复制操作的标准时除了源对象是函数参数这一事实之外,并且要复制的对象由左值重载指定首先执行为副本选择构造函数的解析就好像对象是由右值指定的一样。如果过载解决方案失败。。。

这要求只有当返回表达式的类型与函数返回类型相同时,才能获得隐式移动。

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

当满足省略复制/移动操作的标准时,但是不适用于异常声明,并且要复制的对象是由左值指定,或者当return语句中的表达式是一个(可能带括号(id表达式,它用正文中声明的自动存储持续时间或最里面的封闭函数的参数声明子句lambda表达式,用于选择构造函数的重载解析首先执行复制,就好像对象是由右值。如果第一次过载解决失败或未执行。。。

这种修改基本上允许返回表达式类型可转换为函数返回类型,并且仍然有资格进行隐式移动。

这是否意味着代码需要基于__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不会伤害任何东西。

有一次,即使在以C++14为目标的情况下,你也可能倾向于使用免费的move,如果没有它,事情仍然在C++11中编译,并调用昂贵的复制转换,而不是移动转换。在这种情况下,意外地在C++11下编译会引入一个静默的性能错误。并且当在C++14下编译时,免费的move仍然没有任何不利影响。

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