[英]Cost of Default parameters in C++
我偶然发现了Scott Meyers的“ 嵌入式环境中的有效C ++”中的一个例子,其中描述了两种使用默认参数的方法:一种被描述为昂贵而另一种被描述为更好的选择。
我错过了为什么第一个选项可能比另一个更昂贵的解释。
void doThat(const std::string& name = "Unnamed"); // Bad
const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
在第一个中, 每次在没有参数的情况下调用函数时 ,从文本"Unnamed"
初始化临时std::string
。
在第二种情况下,对象defaultName
初始化一次 (每个源文件),并且只在每次调用时使用。
void doThat(const std::string& name = "Unnamed"); // Bad
这是“坏”的,因为每次调用doThat()
时doThat()
创建一个内容为"Unnamed"
的新std::string
。
我说“坏”并且不错,因为我使用的每个C ++编译器中的小字符串优化都会将"Unnamed"
数据放在呼叫站点创建的临时std::string
,而不为它分配任何存储空间。 因此,在这种特定情况下,临时参数的成本很低。 该标准不需要小字符串优化,但它明确地设计为允许它,并且当前使用的每个标准库都实现它。
较长的字符串会导致分配; 小字符串优化仅适用于短字符串。 分配是昂贵的; 如果您使用经验法则,一次分配比普通指令( 多微秒! )贵1000倍以上,那么您就不会太远了。
const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
这里我们创建一个全局defaultName
,其内容为"Unnamed"
。 这是在静态初始化时创建的。 这里有一些风险; 如果在静态初始化或销毁时( main
运行之前或之后)调用doThat
,则可以使用未defaultName
或已经销毁的defaultName
调用它。
另一方面,这里不存在每次调用内存分配的风险。
现在,现代c ++ 17中的正确解决方案是:
void doThat(std::string_view name = "Unnamed"); // Best
即使字符串很长也不会分配; 它甚至不会复制字符串! 最重要的是,在999/1000个案例中,这是旧的doThat
API的替代品,它甚至可以在您将数据传递到doThat
而不依赖于默认参数时提高性能。
此时,嵌入式中的c ++ 17支持可能不存在,但在某些情况下可能很快就会出现。 字符串视图是一个足够大的性能提升,有许多类似的类型已经在野外做同样的事情。
但教训仍然存在; 不要在默认参数中执行昂贵的操作。 在某些情况下(特别是嵌入式世界),分配可能会很昂贵。
也许我误解了“代价高昂”(对于“正确的”解释,请参见另一个答案),但是默认参数需要考虑的一件事是它们在这样的情况下不能很好地扩展:
void foo(int x = 0);
void bar(int x = 0) { foo(x); }
一旦你添加了更多的嵌套,这就变成了一个容易出错的噩梦,因为默认值必须在几个地方重复(即,在一个微小的改变需要改变代码中的不同位置的意义上代价很高)。 避免这种情况的最佳方法就像在您的示例中:
const int foo_default = 0;
void foo(int x = foo_default);
void bar(int x = foo_default) { foo(x); } // no need to repeat the value here
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.