C ++中的默认参数成本

时间:2018-02-20 08:45:23

标签: 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

3 个答案:

答案 0 :(得分:56)

在第一个中,每次<{1}} 初始化临时std::string 在没有参数的情况下调用函数。

在第二种情况下,对象"Unnamed"初始化一次(每个源文件),并且只在每次调用时使用。

答案 1 :(得分:18)

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

这很糟糕,因为每次调用std::string时都会创建一个内容为"Unnamed"的新doThat()

我说“坏”而不是因为我使用的每个C ++编译器中的小字符串优化都会将"Unnamed"数据放在创建于的std::string临时const std::string defaultName = "Unnamed"; void doThat(const std::string& name = defaultName); // Better 内呼叫站点并没有为它分配任何存储空间。因此,在这个特定的情况下,临时参数的成本很低。该标准不需要小字符串优化,但它明确设计为允许它,并且当前使用的每个标准库都实现它。

较长的字符串会导致分配;小字符串优化仅适用于短字符串。分配是昂贵的;如果你使用经验法则,一个分配比通常的指令(multiple microseconds!)贵1000倍以上,那么你就不会太远了。

defaultName

在这里,我们创建一个内容为"Unnamed"的全局doThat。这是在静态初始化时创建的。这里有一些风险;如果在静态初始化或销毁时(main运行之前或之后)调用defaultName,则可以使用未构造的void doThat(std::string_view name = "Unnamed"); // Best 或已经销毁的doThat调用它。

另一方面,此处不存在每次调用内存分配的风险。

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

doThat

即使字符串很长也不会分配;它甚至不会复制字符串!最重要的是,在999/1000个案例中,这是旧public interface UserClient { @FormUrlEncoded @POST(Constants.REGISTRATION_URL) Call<UserData> createAccount( @FieldMap Map<String, Object> map ); } API的替代品,当您将数据传递到 private void createUserForm(String username, String password, String email, String deviceID, String gender, String avatarPath, int online){ UserClient userClient = retrofit.create(UserClient.class); Map<String, Object> map = new HashMap<>(); map.put("username", username); map.put("password", password); Call<UserData> call = userClient.createAccount(map); call.enqueue(new Callback<UserData>() { @Override public void onResponse(Call<UserData> call, Response<UserData> response) { // Need to get data from server after user has been inserted in database } @Override public void onFailure(Call<UserData> call, Throwable t) { } }); } 并且不依赖于默认参数时,它甚至可以提高性能

此时,嵌入式中的支持可能不存在,但在某些情况下可能会很快。而且字符串视图的性能提升得足够大,以至于已经存在大量类似的类型,它们可以做同样的事情。

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

答案 2 :(得分:3)

也许我误解了“代价高昂”(对于“正确的”解释,请参见另一个答案),但默认参数需要考虑的一点是它们在这样的情况下不能很好地扩展:

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