现代C ++可以免费获得你的performance吗?

有时声称,即使仅仅编译C ++ 98代码,C ++ 11/14也可以提高性能。 理由通常是沿着移动语义的线,因为在某些情况下,右值构造函数是自动生成的或现在是STL的一部分。 现在我想知道这些情况以前是否已经由RVO或类似的编译器优化处理过。

我的问题是,如果你能给我一个C ++ 98代码的实例,不加修改,使用支持新语言function的编译器运行得更快。 我明白一个标准的编译器不需要做copy copy,因此移动语义可能会带来速度,但是如果你愿意的话,我希望看到一个较less的病态的情况。

编辑:只是要清楚,我不问新的编译器是否比旧的编译器更快,而是如果有代码,将-std = c ++ 14添加到我的编译器标志将会运行得更快(避免副本,但如果你除了移动语义之外还能想出其他的东西,我也会感兴趣的)

我知道5个一般的类别,重新编译C ++ 03编译器,因为C ++ 11可能会导致无限的性能提高,这与实现的质量无关。 这些都是移动语义的变体。

std::vector重新分配

 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 

每当foo的缓冲区在C ++ 03中重新分配时,它都会复制bar每个vector

在C ++ 11中,它移动了bar::data s,它基本上是免费的。

在这种情况下,这依赖于std容器vector优化。 在下面的每种情况下,使用std容器只是因为它们是C ++对象,在升级编译器时,它们会自动“在C ++ 11中有效地move语义”。 不阻止包含std容器的对象也inheritance自动改进的move构造函数。

NRVO失败

当NRVO(named return value optimization)失败时,在C ++ 03中拷贝,在C ++ 11上运行。 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; } 

我们有三个值 – 函数中的返回值和两个不同的值。 Elision允许函数中的值与返回值“合并”,但不与其他值一起使用。 它们都不能与没有合并的返回值合并。

基本问题是,NRVO elision是脆弱的,并且不在return站点附近的变化的代码可以突然在该位置性能大幅降低而没有诊断发射。 在大多数NRVO失败的情况下,C ++ 11最终会有一个move ,而C ++ 03最终会有一个副本。

返回一个函数参数

Elision在这里也是不可能的:

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

在C + + 11这是便宜的:在C + + 03没有办法避免复制。 函数的参数不能用返回值消除,因为参数和返回值的生存期和位置由调用代码pipe理。

但是,C ++ 11可以从一个移动到另一个。 (在一个较less的玩具的例子中,可能会做一些事情)。

push_backinsert

最后,容器中的事件不会发生:但是,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中创build一个临时的,然后它被复制到向量v 。 2个std::string缓冲区被分配,每个具有相同的数据,一个被丢弃。

在C ++ 11中,创build一个临时任务。 whatever&& push_back超载,然后move临时move向量v 。 一个std::string缓冲区被分配,并被移入向量中。 一个空的std::string被放弃。

分配

从下面的@ Jarod42的答案中被盗。

Elision不能在分配时发生,但是可以从中移出。

 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 ,它基本上是免费的。


为了达到上述效果,您需要一个编译器来为您合成移动构造函数和赋值。

MSVC 2013实现在std容器中移动构造函数,但是不会在你的types上合成移动构造函数。

所以包含std::vector和类似的types在MSVC2013中没有得到这样的改进,但是将会开始在MSVC2015中获得它们。

clang和gcc早已实现了隐式的移动构造函数。 如果您传递-Qoption,cpp,--gen_move_operations (为了与MSVC2013交叉兼容,默认情况下他们不这样做),英特尔的2013编译器将支持隐式生成移动构造函数。

如果你有这样的东西:

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

您在C ++ 03中获得了一份副本,而您在C ++ 11中获得了一个移动任务。 所以你在这种情况下有免费的优化。