常量值和RVO

4

假设我有这个函数:

template <class A>
inline A f()
{
  A const r(/* a very complex and expensive construction */);

  return r;
}

声明 rconst 是一个好主意吗?因为 const 变量不能被移动。请注意,返回的值并不是 const。我正在纠结的问题是,虽然 r 确实是 const,但将其声明为 const 可能不是一个好主意。然而,这个限定符应该帮助编译器生成更好的代码。

2
上面的 inline 是多余的。 - Yakk - Adam Nevraumont
3
@user1095108,这些函数也被隐含地视为内联函数,只有在类体内定义的函数(包括友元函数)。 - chris
2
自从template函数被添加到语言中以来,它们就隐式地成为了inline函数。inline主要意味着“来自不同编译单元的冲突定义将被静默丢弃”,这在大多数使用template函数的情况下都是一种要求。 - Yakk - Adam Nevraumont
4
template函数不会隐式地成为 inline 函数。它们具有允许多个相同定义的语义,这是 inline 关键字必需的行为之一,但它们可能不会自动具有 inline 提供的其他实现特定的行为。 - Ben Voigt
1
@user1095108 在你的问题中没有提到内联的事情。一个函数是否被内联不应该影响它的可观察行为。 - Neil Kirk
显示剩余5条评论
2个回答

7
这里所示,NRVO消除了由return r;这一行所暗示的r的复制。
#include <iostream>

struct A {
  const char* name;
  A( const char* name_ ):name(name_) { std::cout << "created " << name << "\n"; }
  A(A const&){ std::cout << "copied " << name << "\n"; }
  A(A &&){ std::cout << "moved " << name << "\n"; }
};

A f() {
  std::cout << "start of f()\n";
  A const r("bob");
  std::cout << "body of f()\n";
  return r;
}

int main() {
  A x = f();
}

而且,在main中的副本也被省略了。

如果您以其他方式阻止NRVO和RVO(例如在使用GCC编译时使用标志-fno-elide-constructors),那么const可能会导致对象被复制而不是移动。如果我们A中删除复制构造函数,可以看到这一点:

#include <iostream>

struct A {
  const char* name;
  A( const char* name_ ):name(name_) { std::cout << "created " << name << "\n"; }
  //A(A const&){ std::cout << "copied " << name << "\n"; }
  A(A &&){ std::cout << "moved " << name << "\n"; }
};

A f() {
  std::cout << "start of f()\n";
  A const r("bob");
  std::cout << "body of f()\n";
  return r;
}

int main() {
  A x = f();
}

代码不再编译。尽管只要NRVO发生,就不会执行副本构造函数,但您的const局部变量需要其存在。
现在,NRVO需要一些东西,例如在问题函数的每条执行路径上返回单个变量:如果您“中止”并执行return A(),则NRVO被阻塞,并且您的const局部变量突然强制在所有返回位置进行复制。

其实没有必要拥有一个复制构造函数。http://ideone.com/n867vx - Ben Voigt
2
@user1095108 不是这样的。如果发生 NRVO,只有通过复制或移动构造函数的要求才能区分 const 和非 constconst 表示必须存在复制构造函数,而非 const 表示必须存在移动或复制构造函数。如果发生 NRVO,则不会调用任何一个构造函数,无论变量是 const 还是非 const。NRVO 不被标准保证,但每个主要的编译器都会执行它。 - Yakk - Adam Nevraumont
1
@BenVoigt 是的,但 const 移动构造函数有点不太好搞定。我的意思是,真的没有变异的移动吗?接下来我们会有什么,没有 template 元编程就能保证编译时函数求值的情况吗? - Yakk - Adam Nevraumont
@Yakk: 你觉得constexpr也是一个很麻烦的边角情况吗? - Ben Voigt
3
我知道我不应该仅仅因为发表评论而进行+1。但是这个+1是来自 C++11 中移动语义的首席作者。我认为这值得一条评论。 :-) - Howard Hinnant
显示剩余6条评论

1
如果您可以控制 class A,并且希望通过移动返回 const 对象,您可以这样做。
mutable bool resources_were_stolen = false;

将其翻译成中文是:

并在 const 移动构造函数中将其设置为 true


A(const A&& other) { ...; other.resources_were_stolen = true; }
~A() { if (!resources_were_stolen) ... }

实际上,析构函数可能会变成if (resources_were_stolen) some_unique_ptr.release();,利用对象在构造和析构期间失去const属性的事实。

你认为返回一个非const值比返回一个const值略微更好吗?我知道“移动”不一定比“复制”更“便宜”。 - user1095108

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