当返回std::pair时,是否会发生RVO?

9
一个函数需要向调用者返回两个值,最好的实现方式是什么?
选项1:
pair<U,V> myfunc()
{
...
return make_pair(getU(),getV());
}

pair<U,V> mypair = myfunc();

选项 1.1:

// Same defn
U u; V v;
tie(u,v) = myfunc();

选项2:
void myfunc(U& u , V& v)
{
u = getU(); v= getV();
}

U u; V v;
myfunc(u,v);

我知道使用Option2不会有复制/移动操作,但是它看起来很丑陋。在Option1和1.1中是否会发生任何复制/移动操作?假设U和V是支持复制/移动操作的大对象。

问:根据标准,是否理论上可能进行RVO / NRVO优化?如果是,是否已经有gcc或其他编译器实现了?


2
我不知道关于std::pair的任何事情会阻止RVO / NRVO。通常很容易通过包含一个复制构造函数来测试,该构造函数告诉您何时发生复制。 - Jerry Coffin
1
g++ 实现了 RVO,可以防止复制 pair,但是仍然需要将 u 和 v 复制到 pair 中。 - Vaughn Cato
我进行了一些测试,并发现使用g++编译器,哪个更快很大程度上取决于可以进行的内联操作以及U和V的复制构造函数的复杂性。如果你只是想要性能,我认为你需要对其进行分析以确定哪个最快。 - Vaughn Cato
这并不是回答你问题的真正答案,但我建议使用简单的结构体而不是pair。Pair很丑陋且过于笨重。我曾经看到过像time.first; time.second;这样的代码,难以阅读。time.minutes; time.seconds;要好得多。 - Kocka
4个回答

8

当返回std::pair时,会发生RVO吗?

是的,可能会发生。

这是否有保证?

不,没有保证。


C++11标准:第12.8/31节:

当满足某些条件时,实现允许省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。

复制省略不是一个保证的功能。编译器可以在任何时候进行优化。关于std::pair没有什么特别之处。如果编译器足够聪明,能够检测到优化机会,它就会这样做。因此,您的问题是与编译器相关的,但是同样的规则适用于std::pair和其他任何类。


4

虽然RVO不能保证,但在C++11中,我认为您所定义的函数必须至少进行移动返回,因此我建议保留更清晰的定义,而不是将其扭曲以接受输出变量(除非您有使用它们的特定策略)。

此外,即使此示例使用了RVO,您明确使用make_pair意味着您始终至少有一个额外的对构造和移动操作。将其更改为返回大括号初始化的表达式:

return { getU(), getV() };

1

RVO或复制省略取决于编译器,因此如果您想要使用RVO并避免调用复制构造函数,则最好的选择是使用指针。

在我们的产品中,我们使用指针和boost容器指针来避免复制构造函数。这确实可以提高约10%的性能。

回到您的问题, 在选项1中,由于您没有返回U或V而是返回std :: pair对象,因此不会调用U和V的复制构造函数,大多数编译器肯定会在此处使用RVO以避免该问题。

谢谢 Niraj Rathi


1
如果您在创建对后需要对uv进行其他工作,我发现以下模式在C++17中非常灵活:
pair<U,V> myfunc()
{
  auto out = make_pair(getU(),getV());
  auto& [u, v] = out;
  // Work with u and v
  return out;
}

这应该是编译器使用命名返回值优化的一个很简单的案例。

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