简体   繁体   English

C ++中的默认参数成本

[英]Cost of Default parameters in C++

I stumbled through an example from " Effective C++ in an Embedded Environment " by Scott Meyers where two ways of using default parameters were described: one which was described as costly and the other as a better option. 我偶然发现了Scott Meyers的“ 嵌入式环境中的有效C ++”中的一个例子,其中描述了两种使用默认参数的方法:一种被描述为昂贵而另一种被描述为更好的选择。

I am missing the explanation of why the first option might be more costly compared to the other one. 我错过了为什么第一个选项可能比另一个更昂贵的解释。

void doThat(const std::string& name = "Unnamed"); // Bad

const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better

In the first one, a temporary std::string is initialised from the literal "Unnamed" each time the function is called without an argument. 在第一个中, 每次在没有参数的情况下调用函数 ,从文本"Unnamed"初始化临时std::string

In the second case, the object defaultName is initialised once (per source file), and simply used on each call. 在第二种情况下,对象defaultName初始化一次 (每个源文件),并且只在每次调用时使用。

void doThat(const std::string& name = "Unnamed"); // Bad

This is "bad" in that a new std::string with the contents "Unnamed" is created every time doThat() is called. 这是“坏”的,因为每次调用doThat()doThat()创建一个内容为"Unnamed"的新std::string

I say "bad" and not bad because the small string optimization in every C++ compiler I've used will place the "Unnamed" data within the temporary std::string created at the call site and not allocate any storage for it. 我说“坏”并且不错,因为我使用的每个C ++编译器中的小字符串优化都会将"Unnamed"数据放在呼叫站点创建的临时std::string ,而不为它分配任何存储空间。 So in this specific case, there is little cost to the temporary argument. 因此,在这种特定情况下,临时参数的成本很低。 The standard does not require the small string optimization, but it is explicitly designed to permit it, and every standard library currently in use implements it. 该标准不需要小字符串优化,但它明确地设计为允许它,并且当前使用的每个标准库都实现它。

A longer string would cause an allocation; 较长的字符串会导致分配; the small string optimization works on short strings only. 小字符串优化仅适用于短字符串。 Allocations are expensive; 分配是昂贵的; if you use the rule of thumb that one allocation is 1000+ times more expensive than a usual instruction ( multiple microseconds! ), you won't be far off. 如果您使用经验法则,一次分配比普通指令( 多微秒! )贵1000倍以上,那么您就不会太远了。

const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better

Here we create a global defaultName with the contents "Unnamed" . 这里我们创建一个全局defaultName ,其内容为"Unnamed" This is created at static initialization time. 这是在静态初始化时创建的。 There are some risks here; 这里有一些风险; if doThat is called at static initialization or destruction time (before or after main runs), it could be invoked with an unconstructed defaultName or one that has already been destroyed. 如果在静态初始化或销毁时( main运行之前或之后)调用doThat ,则可以使用未defaultName或已经销毁的defaultName调用它。

On the other hand, there is no risk that a per-call memory allocation will occur here. 另一方面,这里不存在每次调用内存分配的风险。


Now, the right solution in modern is: 现在,现代的正确解决方案是:

void doThat(std::string_view name = "Unnamed"); // Best

which won't allocate even if the string is long; 即使字符串很长也不会分配; it won't even copy the string! 它甚至不会复制字符串! On top of that, in 999/1000 cases this is a drop-in replacement to the old doThat API and it can even improve performance when you do pass data into doThat and not rely on the default argument. 最重要的是,在999/1000个案例中,这是旧的doThat API的替代品,它甚至可以在您将数据传递到doThat而不依赖于默认参数时提高性能。

At this point, support in the embedded may not be there, but in some cases it could be shortly. 此时,嵌入式中的支持可能不存在,但在某些情况下可能很快就会出现。 And string view is a large enough performance increase that there are a myriad of similar types already in the wild that do the same thing. 字符串视图是一个足够大的性能提升,有许多类似的类型已经在野外做同样的事情。

But the lesson still remains; 但教训仍然存在; don't do expensive operations in default arguments. 不要在默认参数中执行昂贵的操作。 And allocation can be expensive in some contexts (especially the embedded world). 在某些情况下(特别是嵌入式世界),分配可能会很昂贵。

Maybe I misinterpret "costly" (for the "correct" interpretation see the other answer), but one thing to consider with default parameters is that they dont scale well in situations like that: 也许我误解了“代价高昂”(对于“正确的”解释,请参见另一个答案),但是默认参数需要考虑的一件事是它们在这样的情况下不能很好地扩展:

void foo(int x = 0);
void bar(int x = 0) { foo(x); }

This becomes an error prone nightmare once you add more nesting because the default value has to be repeated in several places (ie costly in the sense that one tiny change requires to change different places in the code). 一旦你添加了更多的嵌套,这就变成了一个容易出错的噩梦,因为默认值必须在几个地方重复(即,在一个微小的改变需要改变代码中的不同位置的意义上代价很高)。 The best way to avoid that is like in your example: 避免这种情况的最佳方法就像在您的示例中:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM