我偶然发现了Scott Meyers的“嵌入式环境中的有效C ++ ”中的一个例子,其中描述了两种使用默认参数的方法:一种被描述为昂贵而另一种被描述为更好的选择
我错过了解释为什么第一个选项可能比另一个选项更昂贵的原因。
void doThat(const std::string& name = "Unnamed"); // Bad
const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
答案 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
调用它。
另一方面,此处不存在每次调用内存分配的风险。
现在,现代c++17中的正确解决方案是:
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) {
}
});
}
并且不依赖于默认参数时,它甚至可以提高性能
此时,嵌入式中的c++17支持可能不存在,但在某些情况下可能会很快。而且字符串视图的性能提升得足够大,以至于已经存在大量类似的类型,它们可以做同样的事情。
但是教训仍然存在;不要在默认参数中执行昂贵的操作。在某些情况下(特别是嵌入式世界),分配可能会很昂贵。
答案 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