使用C ++ 11的“自动”可以提高性能吗?

我可以看到为什么C ++ 11中的autotypes提高了正确性和可维护性。 我读过,它也可以提高性能( 几乎总是自动通过香草萨特),但我想念一个很好的解释。

  • auto如何提高性能?
  • 任何人都可以举个例子吗?

auto可以通过避免静默隐式转换来帮助性能。 我发现一个令人信服的例子如下。

 std::map<Key, Val> m; // ... for (std::pair<Key, Val> const& item : m) { // do stuff } 

看到错误? 在这里,我们认为我们正在通过const引用来优雅地获取地图中的每个项目,并使用新的range-forexpression式来使我们的意图清晰,但实际上我们正在复制每个元素。 这是因为std::map<Key, Val>::value_typestd::pair<const Key, Val> ,而不是std::pair<Key, Val> 。 因此,当我们(隐含地)有:

 std::pair<Key, Val> const& item = *iter; 

而不是参考一个现有的对象,并把它留在那,我们必须做一个types转换。 只要有可用的隐式转换,就可以对一个不同types的对象(或临时对象)进行const引用,例如:

 int const& i = 2.0; // perfectly OK 

types转换是一个允许的隐式转换,因为你可以将一个const Key转换成一个Key ,但是我们必须构造一个临时的新types来允许这个。 因此,我们的循环有效:

 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对象,只是为了说明,实际上这个未命名的临时对象只是在其生命周期中绑定的)。

只要改成:

 for (auto const& item : m) { // do stuff } 

只是为我们节省了大量的拷贝 – 现在引用的types与初始化types相匹配,所以不需要临时或转换,我们可以直接引用。

因为auto推导初始化expression式的types,所以不涉及types转换。 结合模板化的algorithm,这意味着您可以得到更直接的计算,比您自己编写一个types更为直接 – 尤其是在处理不能指定types的expression式时!

一个典型的例子来自(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?); 

使用cmp2cmp3 ,整个algorithm可以内联比较调用,而如果构buildstd::function对象,则不仅可以调用内联,还必须在types擦除的内部中进行多态查找的函数包装器。

这个主题的另一个变种是你可以说:

 auto && f = MakeAThing(); 

这总是一个引用,绑定到函数调用expression式的值,并且永远不会构造任何附加对象。 如果你不知道返回值的types,你可能会被迫通过像T && f = MakeAThing()这样的东西来构造一个新的对象(可能是临时的T && f = MakeAThing() 。 (而且, auto &&甚至在返回types不可移动且返回值为prvalue时起作用。

有两个类别。

auto可以避免types擦除。 有不可思议的types(如lambdas)和几乎不可能的types(如std::bind或其他expression式模板的结果)。

没有auto ,你最终不得不键入删除数据类似std::function 。 types擦除有成本。

 std::function<void()> task1 = []{std::cout << "hello";}; auto task2 = []{std::cout << " world\n";}; 

task1具有types擦除开销 – 可能的堆分配,内联难度以及虚拟函数表调用开销。 task2没有。 兰姆达斯需要自动或其他forms的types扣除存储没有types擦除; 其他types可能如此复杂以至于实际上只需要它。

其次,你可以得到错误的types。 在某些情况下,错误的types看起来是完美的,但会造成副本。

 Foo const& f = expression(); 

将编译如果expression()返回Bar const&Bar ,甚至Bar& ,其中Foo可以从Bar构build。 一个临时的Foo将被创build,然后绑定到f ,并且其生命周期将被延长直到f消失。

程序员可能意味着Bar const& f ,并不打算在那里创build一个副本,但不pipe副本是什么。

最常见的例子是*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> std::pair<const A, B> 。 (地图上的键是常量,因为编辑它是一个坏主意)

@Barry和@KerrekSB首先在他们的答案中说明了这两个原则。 这只是试图在一个答案中强调两个问题,其目的在于解决问题而不是以示例为中心。

现有的三个答案举例说明了使用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; 

导致了不同的产出。 诚然,这主要是由于Eigens惰性评估,但该代码是/应该是(库)用户透明。

虽然性能在这里没有受到很大的影响,但使用auto来避免无意的pessimization可能被归类为不成熟的优化,或者至less是错误的)。