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 没有副本/移动,但看起来很难看。选项 1、1.1 中是否会发生任何复制/移动?让我们假设 U 和 V 是支持复制/移动操作的巨大对象。

问:理论上是否可以按照标准进行任何 RVO/NRVO 优化?如果是,是否已实现 gcc 或任何其他编译器?

4

4 回答 4

8

返回时会发生RVOstd::pair吗?

是的,它可以。

保证会发生吗?

不它不是。


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

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

复制省略不是保证功能。它是一种优化编译器被允许尽可能执行。没有什么特别的std::pair。如果编译器足以检测到优化机会,它就会这样做。所以你的问题是编译器特定的,但是同样的规则也适用于std::pair任何其他类。

于 2012-12-26T17:06:40.270 回答
4

虽然不能保证 RVO,但在 C++11 中,你定义的函数我相信至少必须移动返回,所以我建议留下更清晰的定义而不是扭曲它以接受输出变量(除非你有使用它们的特定政策)。

此外,即使此示例确实使用了 RVO,您对 make_pair 的显式使用也意味着您将始终拥有至少一个额外的对构造,因此会进行移动操作。更改它以返回一个大括号初始化的表达式:

return { getU(), getV() };
于 2012-12-27T03:07:40.240 回答
1

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

在我们的产品中,我们使用使用指针和提升容器指针来避免复制构造函数。这确实使性能提升了 10% 左右。

提出您的问题,在选项 1 中,不会调用 U 和 V 的复制构造函数,因为您没有返回 U 或 V,而是返回 std::pair 对象,因此将调用它的复制构造函数,并且大多数编译器肯定会在这里使用 RVO 来避免这种情况.

谢谢尼拉吉·拉蒂

于 2012-12-26T17:14:47.973 回答
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;
}

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

于 2020-08-20T08:01:29.333 回答