使用 C++11 的"自动"可以提高性能吗?
Can the use of C++11's 'auto' improve performance?
我明白为什么c++ 11中的auto
类型提高了正确性和可维护性。我读到过它也可以提高性能(Herb Sutter的《Almost Always Auto》),但我没有一个很好的解释。
-
auto
如何提高性能? 谁能举个例子?
auto
可以通过避免静默隐式转换来提高性能。我觉得下面的例子很有说服力。
std::map<Key, Val> m;
// ...
for (std::pair<Key, Val> const& item : m) {
// do stuff
}
看到bug了吗?在这里,我们认为我们优雅地通过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
推导了初始化表达式的类型,所以不涉及类型转换。结合模板化算法,这意味着你可以获得比自己创建类型更直接的计算。特别是当您处理无法命名类型的表达式时!
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()
之类的东西构造一个新对象(可能是临时对象)。(此外,auto &&
甚至可以在返回类型不可移动且返回值为prvalue的情况下工作)
有两类:
auto
可以避免类型擦除。有不可命名的类型(如lambdas),也有几乎不可命名的类型(如std::bind
的结果或其他类似表达式模板的东西)。
如果没有auto
,您最终必须键入erase data down到std::function
之类的内容。类型擦除是有代价的。
std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " worldn";};
task1
具有类型擦除开销——可能的堆分配、难以内联它以及虚拟函数表调用开销。task2
无。lambda 需要自动或其他形式的类型演绎来存储,而不需要类型擦除;其他类型可能非常复杂,只在实践中使用。
第二,你可能得到错误的类型。在某些情况下,错误的类型看起来可以很好地工作,但会导致复制。
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;
导致不同的输出。不可否认,这主要是由于Eigens的惰性求值,但代码对(库)用户是/应该是透明的。
虽然性能在这里没有受到很大的影响,但是使用auto
来避免无意的悲观化可能被归类为过早的优化,或者至少是错误的;)。
- MSVC是否支持C++11样式的属性而不是__declspec
- 创建LinkedList退出,返回代码为-11(SIGSEGV)
- 我可以将一个用clang c++11编译的对象与另一个用c++17编译的对象链接起来吗
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 如何将模板转换为C++11之前的模板
- c++11评估顺序(未定义的行为)
- C++中的VLA,扩展名为std=C++11
- 代码在我的计算机上运行良好,但是在将其提交给coursera时遇到未知的信号11问题
- "类模板示例<int>;"语句对 C++11 是什么意思?
- this_thread::sleep_for和计时时钟之间的关系是否由C++11标准指定
- 如何使用lock_guard在c++11中实现scoped_lock功能
- C++11 中不同类型的对象的 std::array 的替代方案
- 为什么 -mmacosx-version-min=10.10 不阻止使用标记为从 10.11 开始的函数?
- 为什么我的C++代码中出现'Segmentation Fault: 11'行?
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- 别名模板的专业化 C++11 中没有开销的最佳替代方案
- STLPort using C++11
- Qt 5.11.2 (Clang 8.0 (Apple), 64 位), 找不到 QJSEngine 文件
- 在 C++11 中,如何查找并返回以给定字符串开头的字符串向量中的所有项?
- C++11 迭代向量的新方法?