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

问题描述 投票:0回答:3

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

我了解到

unique_ptr
/
shared_ptr
可以用于工厂模式(例如,在c++中使用unique_ptr的工厂模式

同时,我了解到返回值优化(RVO)在许多编译器中都是可能的(例如,在 c++ 中返回 std::vector 的有效方法)。

我更喜欢 RVO,因为它更容易使用返回值而不需要包装

unique_ptr
并且更容易阅读代码,但是,由于 RVO 得不到保证,我不想意外地牺牲性能并且必须使用
unique_ptr 
确保返回值是
move
d 而不是复制的。

是否有任何方法可以显式指定要移动的返回值,这样如果 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 的有效方法),不应该答案是“这取决于”你的功能实现以及你是否可以接受意外的性能成本?

c++ smart-pointers nrvo
3个回答
13
投票

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

该语言从 C++17 开始就已经为您做到了这一点。 如果你有一个类似的结构

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

然后,由于保证复制消除,返回的对象一定会被消除。

如果你有这样的结构

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

那么你要么得到NRVO(命名返回值优化),这是不保证的,要么编译器将移动

obj
,因为标准中有一条规则,所有具有自动存储持续时间的函数本地对象都将被移出函数如果他们有移动构造函数。

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


3
投票

如果 RVO 不可能,则触发一些编译器警告

gcc
(自 v14.1 起)有
-Wnrvo

“如果编译器未在 [class.copy.elision] 允许的上下文中省略从局部变量到函数返回值的复制,则会发出警告。这种省略通常称为命名返回值优化.”

gcc
v14.1 可在 https://godbolt.org/

获取

2
投票

我更喜欢 RVO,因为它更容易使用返回值而无需包装 unique_ptr

如果没有 RVO、NRVO 或隐式移动(以防 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
}
© www.soinside.com 2019 - 2024. All rights reserved.