我明白为什么c++ 11中的auto类型提高了正确性和可维护性。我读到过,它也可以提高性能(几乎总是自动的Herb Sutter),但我错过了一个很好的解释。
如何自动提高性能? 有人能举个例子吗?
我明白为什么c++ 11中的auto类型提高了正确性和可维护性。我读到过,它也可以提高性能(几乎总是自动的Herb Sutter),但我错过了一个很好的解释。
如何自动提高性能? 有人能举个例子吗?
因为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?);
使用cmp2和cmp3,整个算法可以内联比较调用,而如果构造std::function对象,不仅不能内联调用,而且还必须在函数包装器的类型擦除内部进行多形查找。
这个主题的另一种变体是你可以说:
auto && f = MakeAThing();
这始终是一个引用,绑定到函数调用表达式的值,并且从不构造任何其他对象。如果您不知道返回值的类型,您可能会被迫通过T && f = MakeAThing()这样的方法构造一个新对象(可能是临时对象)。(此外,当返回类型不是可移动且返回值是prvalue时,auto &&甚至可以工作。)
Auto可以通过避免无声隐式转换来提高性能。下面是一个我觉得很有说服力的例子。
std::map<Key, Val> m;
// ...
for (std::pair<Key, Val> const& item : m) {
// do stuff
}
看到虫子了吗?这里,我们认为我们优雅地通过const引用获取map中的每一项,并使用新的range-for表达式来明确我们的意图,但实际上我们复制了每个元素。这是因为std::map<Key, Val>::value_type是std::pair<const Key, Val>,而不是std::pair<Key, Val>。因此,当我们(含蓄地)有:
std::pair<Key, Val> const& item = *iter;
我们必须进行类型转换,而不是获取对现有对象的引用并将其保留在那里。只要有隐式转换可用,你就可以接受不同类型对象(或临时对象)的const引用,例如:
int const& i = 2.0; // perfectly OK
类型转换是一种允许的隐式转换,原因与你可以将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
(当然,实际上并没有__tmp对象,它只是用于说明,实际上未命名的临时对象只是在其生命周期内绑定到item)。
刚刚换到:
for (auto const& item : m) {
// do stuff
}
这为我们节省了大量的拷贝——现在引用的类型与初始化器类型匹配,所以不需要临时或转换,我们可以直接引用。
有两类。
Auto可以避免类型擦除。有不可命名的类型(比如lambdas)和几乎不可命名的类型(比如std::bind的结果或其他类似表达式模板的东西)。
如果没有auto,你最终不得不输入擦除数据,直到std::function。类型擦除有代价。
std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};
Task1具有类型擦除开销——可能的堆分配、难以内联它以及虚函数表调用开销。Task2没有。Lambdas需要自动或其他形式的类型演绎来存储而不擦除类型;其他类型可能非常复杂,以至于只在实践中需要它。
其次,可能会弄错类型。在某些情况下,错误的类型看似完美,但会导致复制。
Foo const& f = expression();
如果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<const a, B>构造一个std::pair< a, B>。(map上的键是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;
导致不同的输出。不可否认,这主要是由于特征的惰性求值,但该代码对(库)用户是透明的。
虽然性能在这里没有受到很大影响,但使用auto来避免无意的悲观可能会被归类为过早优化,或者至少是错误的;)。