将本地 unique_ptr 作为 shared_ptr 返回

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上的两种情况:GCCClang

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

哪一个是错误的?谢谢。


2
我猜想当我们了解你需要这个的原因时,我们将能够给出更好的答案。目前,这个问题可以通过调用make_shared而不是make_unique来轻松解决。我假设有一些原因导致这种方法不可行,了解这个原因会有所帮助。 - Elliott
7
@Elliott: 可以解决什么问题?他不是在问如何解决任何问题,他想知道他所拥有的代码是否符合标准。 - Benjamin Lindley
2个回答

18

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

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

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

当满足复制操作省略的条件或将满足该条件,但源对象是函数参数时,并且要复制的对象由lvalue指定时,首先执行重载解析以选择用于复制的构造函数,就好像对象被rvalue指定一样。如果重载解析失败,...

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

但是CWG 1579改变了此规定,并且在C++11之后被接受,在C++14及时完成。此段落现在如下:

当满足复制/移动操作的省略条件,但不适用于exception-declaration时,并且要复制的对象由lvalue指定,或return语句中的expression是一个(可能被括起来的)id-expression,该名称命名了在最内层封闭函数或parameter-declaration-clauselambda-expression的主体中声明的具有自动存储期的对象,则首先执行重载解析以选择用于复制的构造函数,就好像对象被rvalue指定一样。如果第一个重载解析失败或未执行,则...

此修改基本上允许返回表达式类型为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并不会造成任何损害。

即使针对C++14,有一种情况可能会倾向于过度使用move,那就是如果不使用它,仍然在C++11中编译,并引用了昂贵的copy转换,而不是move转换。在这种情况下,意外地在C++11中编译将引入静默的性能错误。而在C++14下编译,过度使用move仍然没有任何不良影响。


如果您只使用C++11,我建议在返回之前创建正确的类型,而不是在代码中使用“return std::move(...” 。因为编译器可能会执行NRVO而不是多次移动。 - Dominik Grabiec

10

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 - Guillaume Racicot

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