DaB*_*ain 225 c++ performance auto c++11
我可以看到为什么auto
C++ 11中的类型提高了正确性和可维护性.我读过它也可以提高性能(Herb Sutter 几乎总是自动),但我错过了一个很好的解释.
auto
提高性能?Bar*_*rry 304
auto
可以通过避免静默隐式转换来提高性能.我发现一个令人信服的例子如下.
std::map<Key, Val> m;
// ...
for (std::pair<Key, Val> const& item : m) {
// do stuff
}
Run Code Online (Sandbox Code Playgroud)
看到这个bug?在这里,我们认为我们优雅地通过const引用获取地图中的每个项目并使用新的range-for表达式来明确我们的意图,但实际上我们正在复制每个元素.这是因为std::map<Key, Val>::value_type
是std::pair<const Key, Val>
,没有std::pair<Key, Val>
.因此,当我们(隐含地)具有:
std::pair<Key, Val> const& item = *iter;
Run Code Online (Sandbox Code Playgroud)
我们必须进行类型转换,而不是引用现有对象并将其留在那里.只要存在可用的隐式转换,就可以对不同类型的对象(或临时)进行const引用,例如:
int const& i = 2.0; // perfectly OK
Run Code Online (Sandbox Code Playgroud)
该类型转换是可以转换一个同样的原因被允许的隐式转换const Key
的Key
,但我们必须建立一个临时的新型的,以允许这一点.因此,我们的循环有效:
std::pair<Key, Val> __tmp = *iter; // construct a temporary of the correct type
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it
Run Code Online (Sandbox Code Playgroud)
(当然,实际上并没有一个__tmp
对象,它只是用于说明,实际上未命名的临时item
用于它的生命周期).
只需改为:
for (auto const& item : m) {
// do stuff
}
Run Code Online (Sandbox Code Playgroud)
刚刚保存了大量的副本 - 现在引用的类型与初始化程序类型匹配,因此不需要临时或转换,我们可以直接引用.
Ker*_* SB 69
因为auto
推导出初始化表达式的类型,所以不涉及类型转换.结合模板化算法,这意味着您可以获得比自己构成类型更直接的计算 - 特别是在处理类型无法命名的表达式时!
一个典型的例子来自(ab)使用std::function
:
std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1); // bad
auto cmp2 = std::bind(f, _2, 10, _1); // good
auto cmp3 = [](T a, T b){ return f(b, 10, a); }; // also good
std::stable_partition(begin(x), end(x), cmp?);
Run Code Online (Sandbox Code Playgroud)
使用cmp2
和cmp3
,整个算法可以内联比较调用,而如果你构造一个std::function
对象,不仅不能内联调用,而且你还必须在函数包装器的类型擦除内部中进行多态查找.
这个主题的另一个变体是你可以说:
auto && f = MakeAThing();
Run Code Online (Sandbox Code Playgroud)
这始终是一个引用,绑定到函数调用表达式的值,并且永远不会构造任何其他对象.如果您不知道返回值的类型,您可能会被迫通过类似的东西构造一个新对象(可能是一个临时对象)T && f = MakeAThing()
.(此外,auto &&
即使返回类型不可移动且返回值为prvalue ,也能正常工作.)
Yak*_*ont 41
有两类.
auto
可以避免类型擦除.有不可思议的类型(如lambda)和几乎不可知的类型(如结果std::bind
或其他表达模板之类的东西).
如果没有auto
,您最终必须将数据类型删除到类似的东西std::function
.类型擦除有成本.
std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};
Run Code Online (Sandbox Code Playgroud)
task1
具有类型擦除开销 - 可能的堆分配,难以内联它,以及虚函数表调用开销. task2
没有.Lambdas 需要自动或其他形式的类型扣除来存储而不需要类型擦除; 其他类型可能非常复杂,以至于他们在实践中只需要它.
其次,你可以得到错误的类型.在某些情况下,错误的类型看似完美,但会产生副本.
Foo const& f = expression();
Run Code Online (Sandbox Code Playgroud)
如果expression()
返回Bar const&
或Bar
甚至可以从中构造Bar&
,将编译.临时将被创建,然后被绑定,并且它的生命周期将被延长直到消失.Foo
Bar
Foo
f
f
程序员可能意味着Bar const& f
而不打算在那里制作副本,但无论如何都要制作副本.
最常见的例子就是类型*std::map<A,B>::const_iterator
,这是std::pair<A const, B> const&
没有std::pair<A,B> const&
,但错误是错误是悄无声息的性能价格比的一类.你可以构建std::pair<A, B>
一个std::pair<const A, B>
.(地图上的键是const,因为编辑它是一个坏主意)
@Barry和@KerrekSB都在他们的答案中首次阐述了这两个原则.这只是尝试在一个答案中突出这两个问题,其中的措辞旨在解决问题而不是以实例为中心.
现有的三个答案给出了一些例子,其中使用auto
帮助"使其不太可能无意中感到悲观",从而有效地使其"提高绩效".
这枚硬币还有另一面.使用auto
具有不返回基本对象的运算符的对象可能导致不正确(仍可编译和可运行)代码.例如,这个问题询问如何使用auto
特征库给出不同的(不正确的)结果,即以下行
const auto resAuto = Ha + Vector3(0.,0.,j * 2.567);
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567);
std::cout << "resAuto = " << resAuto <<std::endl;
std::cout << "resVector3 = " << resVector3 <<std::endl;
Run Code Online (Sandbox Code Playgroud)
导致不同的输出.不可否认,这主要是由于Eigens懒惰评估,但该代码对于(库)用户是透明的.
虽然这里的性能没有受到太大影响,但是使用auto
以避免无意的悲观可能被归类为过早优化,或者至少是错误的;).