4

在许多情况下,我想创建一个新的数据实例并将其返回给 API 调用者。

我了解到unique_ptr/shared_ptr可用于工厂模式(例如,工厂模式 using unique_ptr in c++

同时,我了解到返回值优化 (RVO) 在许多编译器中都是可能的(例如,Efficient way to return a std::vector in c++)。

我更喜欢 RVO,因为它更容易使用没有包装的返回值unique_ptr并且更容易阅读代码,但是,由于 RVO 没有保证,我不想意外牺牲性能并且必须使用unique_ptr来确保返回值是moved而不是复制。

是否有任何方法可以明确指定要移动的返回值,以便如果 RVO 可能它不会抱怨任何事情,或者如果 RVO 不可能它会触发一些编译器警告?如果这是可能的,我可以安全地摆脱在这种情况下返回 unique_ptr 。

我正在使用 C++17,需要在 macOS 上支持 Apple Clang 11.0,在 Linux 上支持 g++ 9。

编辑:

我仍在学习 C++,并且在发布此问题时没有区分 RVO(返回值优化)和 NRVO(命名返回值优化)。在我看来,NRVO 在工厂方法等模式中更常见和有用,例如:

vector<foo> vec;
// populate data into vec
return vec;

我正在寻找类似的东西return std::move_only(returned_value),如果这个值不能移动(而不是复制移动),它会给我一个编译器警告。也许我应该将我的问题重新表述为:如果不能保证 NRVO,为什么在这个问题中“按值返回”仍然是推荐的方式(在 c++ 中返回 std::vector 的有效方式),答案不应该是“这取决于”您的功能实现以及您是否可以接受意外的性能成本?

4

2 回答 2

5

如何确保执行 RVO 而不是复制?

从 C++17 开始,该语言已经为您完成了这项工作。如果你有一个像

T foo() { /*stuff*/; return T{ /*stuff*/ }; }

然后,由于保证复制省略,可以保证省略返回的对象。

如果你有一个像

T foo() 
{
    T obj{ /*stuff*/ }; 
    // do stuff with obj
    return obj;
}

然后你会得到 NRVO(命名返回值优化),这是不保证的,或者编译器会移动obj,因为标准中有一条规则,所有具有自动存储持续时间的函数本地对象都将被移出函数,如果它们有移动构造函数。

这意味着您获得副本的唯一时间是如果您返回一个无法优化的对象(它是命名的本地对象或函数参数)并且它不支持移动。全局对象总是被复制,因为它们没有作用于函数。

于 2020-03-05T13:44:52.473 回答
2

我更喜欢 RVO,因为在没有包装 unique_ptr 的情况下使用返回值更容易

如果 NRVO 不可行,则不能返回没有 RVO、NRVO 或隐式移动的 unique_ptr。它不可复制:

std::unique_ptr<int> ptr1;
std::unique_ptr<int> ptr2;
ptr2 = ptr1; // error: not copyable

这不编译。如果不是 RVO、NRVO 或 move,这也不会编译:

std::unique_ptr<int> foo()
{
    return std::unique_ptr<int>{};
}

在这种情况下,这是由于 C++17 有保证的 RVO。但即使没有 RVO,你仍然会得到一个移动而不是副本。

如果它不是用于 NRVO 或保证移动回退,则不会编译:

std::unique_ptr<int> foo()
{
    std::unique_ptr<int> ptr;
    return ptr;
}

因此,您已经依赖于 RVO、NRVO 或移动。不需要 unique_ptr。如果您的类型是可移动的,您可以确保即使在 NRVO 不可能的情况下也不会执行任何副本,例如并非所有return语句都返回相同的本地对象:

std::unique_ptr<int> foo(const bool flag)
{
    if (flag) {
        std::unique_ptr<int> ptr1;
        return ptr; // implicit move
    }
    std::unique_ptr<int> ptr2;
    return ptr2; // implicit move
}
于 2020-03-05T13:31:15.117 回答