call by-const-reference
call by-value
two-overloads
std::string_view
forwarding references
1
call by-const-reference
struct S {
std::string mem;
S(const std::string& s) : mem{s} {}
};
std::string str {"dummy"};
S s1("dummy"); // 1. Implicit ctor
S s2(str); // 2. lvalue
S s3(std::move(str)); // 3. xvalue
S s4(std::string{"dummy"}); // 4. prvalue
2
call by-value
struct S {
std::string mem;
S(std::string s) : mem{std::move(s)} {}
};
3
Two-overloads
struct S {
std::string mem;
S(const std::string& s) : mem{s} {}
S(std::string&& s) : mem{std::move(s)} {}
};
4
C++17 string_view
struct S {
std::string mem;
S(std::string_view s) : mem{s} {}
};
5
Fowarding references
struct S {
std::string mem;
template <class T>
S(T&& s) : mem{ std::forward<T>(s) } {}
};
6
曲未尽
struct S {
using value_type = std::vector<std::string>;
using assoc_type = std::map<std::string, value_type>;
void push_data(std::string_view key, value_type data) {
datasets.emplace(std::make_pair(key, std::move(data)));
}
assoc_type datasets;
};
S s;
s.push_data("key1", {"Dear", "Friend"});
s.push_data("key2", {"Apple"});
s.push_data("key3", {"Jack", "Tom", "Jerry"});
s.push_data("key4", {"20", "22", "11", "20"});
void push_data(auto&& key, auto&& data) {
datasets.emplace(std::make_pair(
std::forward<decltype(key)>(key),
std::forward<decltype(data)>(data)
));
}
6.1
SSO短字符串优化
各家编译器在实现std::string时,基本都会采取一种SSO(Small String Optimization)策略。
此时,对于短字符串,将不会在堆上额外分配内存,而是直接存储在栈上。比如,有些实现会在size的最低标志位上用1代表长字符串,0代表短字符串,根据这个标志位来决定操作形式。
可以通过重载operator new和operator delete来捕获堆分配情况,一个例子如下:
std::size_t allocated = 0;
void* operator new(size_t sz) {
void* p = std::malloc(sz);
allocated += sz;
return p;
}
void operator delete(void* p) noexcept {
return std::free(p);
}
int main() {
allocated = 0;
std::string s("hi");
std::printf("stack space = %zu, heap space = %zu, capacity = %zu\n",
sizeof(s), allocated, s.capacity());
}
例子来源:https://stackoverflow.com/a/28003328
在clang 14.0.0上得出的结果为:
stack space = 32, heap space = 0, capacity = 15
可以看到,对于短字符串,将不会在堆上分配额外的内存,内容实际存在在栈上。
早期版本的编译器可能没有这种优化,但如今的版本基本都有。
也就是说,这时的移动操作实际就相当于复制操作。于是开销就可以如下图表示。
于是可以得出结论:尽管小对象的拷贝操作很快,call by-value还是要慢于其他方式,string_view则是一种较好的方式。
但是,string_view使用起来要格外当心,若你不想为此操心,使用call by-const-reference则是一种不错的方式。
6.2
无拷贝,无移动
6.3
优化限制:Aliasing situations
int foo_by_ref(const S& s) {
int m = s.value;
bar();
int n = s.value;
return m + n;
}
int foo_by_value(S s) {
int m = s.value;
bar();
int n = s.value;
return m + n;
}
例子来源:https://reductor.dev/cpp/2022/06/27/pass-by-value-vs-pass-by-reference.html