C++中的默认参数成本

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初始化一次(每个源文件),并且只在每次调用时使用.

  • @MarioGarcia:C#要求默认参数是编译时常量.这个例子可以工作,因为C#字符串文字是常量,但你不能用更复杂的类型,或者如果你想使用带有字符数组的字符串构造函数.这将产生编译器错误,因此不会有任何运行时成本. (5认同)
  • 所以现在我们有一个字符串结构"每个源文件一次"而不是"每次调用一次".如果经常不使用默认参数,则实际上可能会导致代码大小增加.例如,如果你包含标题来调用一些*other*函数,你可能会得到一个额外的字符串,没有任何理由.斯科特的建议现在看起来更像是"需要考虑的事情",而不是一般"更好". (2认同)

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或已经销毁的调用来调用它.

另一方面,这里不存在每次调用内存分配的风险.


现在,现代的正确解决方案是:

void doThat(std::string_view name = "Unnamed"); // Best
Run Code Online (Sandbox Code Playgroud)

即使字符串很长也不会分配; 它甚至不会复制字符串!最重要的是,在999/1000个案例中,这是旧doThatAPI的替代品,它甚至可以在您将数据传入doThat而不依赖于默认参数时提高性能.

此时,嵌入式中的支持可能不存在,但在某些情况下可能很快就会出现.字符串视图是一个足够大的性能提升,有许多类似的类型已经在野外做同样的事情.

但教训仍然存在; 不要在默认参数中执行昂贵的操作.在某些情况下(特别是嵌入式世界),分配可能会很昂贵.