有时有人声称c++ 11/14可以让你在编译c++ 98代码时获得性能提升。这种解释通常遵循move语义,因为在某些情况下,右值构造函数是自动生成的,或者现在是STL的一部分。现在我想知道这些情况之前是否已经由RVO或类似的编译器优化处理。

我的问题是,你能否给我一个c++ 98代码的实际示例,在不进行修改的情况下,使用支持新语言特性的编译器可以更快地运行。我确实理解,一个符合标准的编译器不需要做复制省略,只是因为这个原因,移动语义可能会带来速度,但我希望看到一个不那么病态的情况,如果你会。

编辑:只是为了澄清,我并不是在问新的编译器是否比旧的编译器更快,而是如果有代码可以将-std=c++14添加到我的编译器标志中,它会运行得更快(避免复制,但如果你能想出除了移动语义之外的任何东西,我也会感兴趣)


如果你有这样的东西:

std::vector<int> foo(); // function declaration.
std::vector<int> v;

// some code

v = foo();

你在c++ 03中得到了一个副本,而在c++ 11中你得到了一个move assignment。 在这种情况下,你有自由优化。


我知道将c++ 03编译器重新编译为c++ 11可以带来无限的性能提升,而这实际上与实现质量无关。这些都是move语义的变体。

std::向量重新分配

struct bar{
  std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03

在c++ 03中,每次foo的缓冲区被重新分配时,它都会复制bar中的每个向量。

在c++ 11中,它会移动::data条,这基本上是免费的。

在这种情况下,这依赖于std容器向量内的优化。在下面的每个例子中,std容器的使用只是因为它们是c++对象,当你升级编译器时,它们在c++ 11中具有高效的“自动”移动语义。包含std容器的不阻塞它的对象也继承自动改进的move构造函数。

NRVO失败

当NRVO(命名为返回值优化)失败时,在c++ 03中它会返回到copy,在c++ 11中它会返回到move。NRVO的失败很容易:

std::vector<int> foo(int count){
  std::vector<int> v; // oops
  if (count<=0) return std::vector<int>();
  v.reserve(count);
  for(int i=0;i<count;++i)
    v.push_back(i);
  return v;
}

甚至:

std::vector<int> foo(bool which) {
  std::vector<int> a, b;
  // do work, filling a and b, using the other for calculations
  if (which)
    return a;
  else
    return b;
}

我们有三个值——返回值,以及函数内的两个不同值。省略允许函数内的值与返回值“合并”,但不允许彼此合并。它们都不能与返回值合并,除非彼此合并。

最基本的问题是NRVO省略是脆弱的,在返回点以外的地方进行更改的代码可能在没有发出诊断信号的情况下突然在该位置出现大量性能下降。在大多数NRVO失败的情况下,c++ 11以移动结束,而c++ 03以复制结束。

返回函数参数

这里也不可能省略:

std::set<int> func(std::set<int> in){
  return in;
}

在c++ 11中,这是很便宜的:在c++ 03中,没有办法避免复制。函数的实参不能与返回值一起省略,因为参数和返回值的生存期和位置由调用代码管理。

然而,c++ 11可以从一种转换到另一种。(在一个不那么玩具的例子中,可能会对集合做一些事情)。

Push_back或insert

最后,省略到容器中不会发生:但c++ 11重载右值移动插入操作符,从而节省了副本。

struct whatever {
  std::string data;
  int count;
  whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );

在c++ 03中,创建一个临时的任意对象,然后将它复制到向量v中。分配std::string缓冲区,每个缓冲区都有相同的数据,其中一个被丢弃。

在c++ 11中,创建的是临时的。然后whatever&& push_back重载将临时对象移动到向量v中。一个std::string缓冲区被分配,并移动到向量中。空的std::string将被丢弃。

赋值

摘自@Jarod42下面的回答。

赋值时不能发生省略,但移动-from可以。

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

这里some_function返回一个要省略的候选对象,但因为它不用于直接构造对象,所以不能省略它。在c++ 03中,上述操作会导致临时对象的内容被复制到some_value中。在c++ 11中,它被移动到some_value中,基本上是免费的。


为了达到上述效果,你需要一个编译器,它为你综合了move构造函数和赋值函数。

MSVC 2013在std容器中实现了move构造函数,但没有在你的类型上合成move构造函数。

因此,包含std::vectors和类似类型的类型在MSVC2013中不会得到这样的改进,但将在MSVC2015中开始得到它们。

Clang和GCC早就实现了隐式move构造函数。Intel的2013编译器将支持隐式生成move构造函数,如果你传递-Qoption,cpp,——gen_move_operations(它们默认不这样做,以努力与MSVC2013交叉兼容)。