ben*_*123 48 c++ performance default-parameters
我偶然发现了Scott Meyers的" 嵌入式环境中的有效C++"中的一个例子,其中描述了两种使用默认参数的方法:一种被描述为昂贵而另一种被描述为更好的选择.
我错过了为什么第一个选项可能比另一个更昂贵的解释.
void doThat(const std::string& name = "Unnamed"); // Bad
const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
Run Code Online (Sandbox Code Playgroud)
Rei*_*ica 56
在第一个中,每次在没有参数的情况下调用函数时,都会std::string从文字初始化临时."Unnamed"
在第二种情况下,对象defaultName初始化一次(每个源文件),并且只在每次调用时使用.
Yak*_*ont 18
void doThat(const std::string& name = "Unnamed"); // Bad
Run Code Online (Sandbox Code Playgroud)
这是"坏"的,因为每次调用都会创建一个std::string带有内容的新内容."Unnamed"doThat()
我说"糟糕"并且不错,因为我使用的每个C++编译器中的小字符串优化都会将"Unnamed"数据放在std::string呼叫站点创建的临时数据中,而不是为它分配任何存储空间.因此,在这种特定情况下,临时参数的成本很低.该标准不需要小字符串优化,但它明确地设计为允许它,并且当前使用的每个标准库都实现它.
较长的字符串会导致分配; 小字符串优化仅适用于短字符串.分配是昂贵的; 如果您使用经验法则,一次分配比普通指令(多微秒!)贵1000倍以上,那么您就不会太远了.
const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
Run Code Online (Sandbox Code Playgroud)
在这里,我们defaultName用内容创建一个全局"Unnamed".这是在静态初始化时创建的.这里有一些风险; 如果doThat在静态初始化或销毁时(main运行之前或之后)调用,则可以使用未构造的defaultName或已经销毁的调用来调用它.
另一方面,这里不存在每次调用内存分配的风险.
现在,现代c ++ 17中的正确解决方案是:
void doThat(std::string_view name = "Unnamed"); // Best
Run Code Online (Sandbox Code Playgroud)
即使字符串很长也不会分配; 它甚至不会复制字符串!最重要的是,在999/1000个案例中,这是旧doThatAPI的替代品,它甚至可以在您将数据传入doThat而不依赖于默认参数时提高性能.
此时,嵌入式中的c ++ 17支持可能不存在,但在某些情况下可能很快就会出现.字符串视图是一个足够大的性能提升,有许多类似的类型已经在野外做同样的事情.
但教训仍然存在; 不要在默认参数中执行昂贵的操作.在某些情况下(特别是嵌入式世界),分配可能会很昂贵.