Я очень чувствителен к распределению памяти и копированию. Итак, если функции требуется входной объект std::string
, я всегда использую const std::string &
.
Недавно я обнаружил, что const std::string &
создаст объект std::string
, если я передам char[]
:
#include <iostream>
#include <string>
using namespace std;
void test_string(const std::string & s) { // use std::string_view here is better
// it can avoid local variable allocation
printf("%p\n", s.data());
}
int main() {
char aa[] = "asd";
test_string(aa);
printf("%p\n", aa);
}
Я изменил const std::string &
на std::string_view
, и это решило проблему ненужной конструкции или копирования. Итак, я думаю, что std::string_view
достоин использования повсюду.
Я заменил все const std::string &
на std::string_view
. Если есть место, где мне нужно использовать std::string
, я использую .data()
, чтобы получить строку.
Проблемы возникают в следующей ситуации:
inline std::vector<std::string_view> Split(std::string_view str, std::string_view delim, const bool trim_empty = false) {
if (str.empty()) return {};
size_t pos, last_pos = 0, len;
std::vector<std::string_view> tokens;
while (true) {
pos = str.find(delim, last_pos);
if (pos == std::string::npos) pos = str.size();
len = pos - last_pos;
if (!trim_empty || len != 0) tokens.push_back(str.substr(last_pos, len));
if (pos == str.size()) break;
else last_pos = pos + delim.size();
}
return tokens;
}
Я использую это, чтобы разделить string
на vector
, как видите, std::string_view
позволяет избежать копирования и выделения большого объема памяти.
Но проблема возникает при использовании std::string_view
:
std::string str = "asd\nbn\ncd\n\n";
std::unordered_map<std::string, int> m;
m["asd"] = 2;
const auto & v = Split(str, "\n");
const auto & it = m.find(v.front().data());
Это не удалось, потому что v.front().data()
возвращает всю строку, а не первую часть.
Я знаю, что это вызвано отсутствием "\0"
в строковом потоке.
Кажется, нет хорошего способа найти правильный std::string_view
, кроме как построить std::string
с помощью std::string(v.front().data())
.
Итак, могу ли я как-нибудь завершить .data()
так, как ожидалось? Или замена const std::string &
на std::string_view
не всегда хороший выбор?
Связано: stackoverflow.com/a/72575650/1387438
«Я очень чувствителен к распределению памяти и копированию...» Я не думаю, что это должно быть в сообщении/вопросе.
Эта конкретная проблема все равно возникла бы, если бы вместо этого у вас был const std::string&
. Если раньше у вас был std::vector<std::string> Split(const std::string& str, const std::string& delim, const bool trim_empty = false)
, он должен был стать std::vector<std::string> Split(std::string_view str, std::string_view delim, const bool trim_empty = false);
, поскольку в векторе нет const std::string&
. Также в C++20 вы можете использовать прозрачный unordered_map.
Если вашей функции нужно, чтобы строка std::string
находилась где-то, передача как std::string_view
будет более расточительной. Если вашей функции требуется, чтобы строка где-то заканчивалась нулевым const char *
, передача as std::string_view
будет нарушена.
Название вашего вопроса не соответствует его телу. Заголовок выглядит как дубликат Когда бы мне передать const& std::string вместо std::string_view?, но ваш вопрос, похоже, больше касается использования std::string_view
для поиска записи в std::unordered_map
, ключи которой равны std::string
, желательно без создания string
.
std::string_view::data()
возвращает указатель, который не обязательно заканчивается нулем.std::string::data()
возвращает указатель, который всегда завершается нулем (начиная с C++11).std::string
Конструктор 9, который принимает char*
, требует, чтобы он указывал на строку с нулевым завершением.
Поскольку вы все равно создаете временный std::string
, делайте это правильно: std::string{v.front()}
создаст строку правильной длины из std::string_view
.
Или, если это единственное использование Split
, не используйте std::string_view
вообще, не будет большой выгоды, если вы будете использовать string_view
только для того, чтобы построить из него string
.
Кроме того, помимо вопроса, но, как заметил Ботье в комментарии , вы используете ссылки неправильно. Оба объекта, на которые они ссылаются, умирают в конце соответствующей строки ;
, и у вас остаются висящие ссылки.
Основная проблема здесь заключается в том, что вы создаете string
из string_view
с единственной целью использовать его с map.find
, и в этом случае вы, вероятно, захотите вызвать конструктор string
, который принимает string_view
вместо передачи char*
, чтобы он не передавал strlen
. нужно вызвать std::string{the_string_view}
. (сделайте std::unordered_map
явно), но это не настоящее решение проблемы.
Обычно find
имеет find
, а компаратор и хеш не допускают других типов, поэтому string_view
не работает для Key
, если std::string
есть std::string
, вам придется определить свой собственный хэшер и компаратор, и вам понадобится C++20 для перегрузки, которая принимает любые введите, аналогичный ключу для найти
#include <unordered_map>
#include <string>
#include <string_view>
#include <iostream>
struct Equality
{
using is_transparent = std::true_type;
bool operator()(const auto& lhs, const auto& rhs) const {
auto result = lhs == rhs;
return result;
}
};
struct Hash
{
using is_transparent = std::true_type;
template <typename T>
size_t operator()(const T& obj) const {
auto result = std::hash<std::decay_t<T>>{}(obj);
return result;
}
};
int main()
{
std::unordered_map<std::string, int, Hash, Equality> m{ {"hel",1} };
std::string s = "hello";
std::string_view sv{ s };
sv = sv.substr(0, 3);
auto it = m.find(sv);
if (it != m.end())
{
std::cout << "found!";
}
}
Обратите внимание, что для запуска этого кода вам понадобится компилятор C++20, в противном случае вам придется использовать версию контейнера Boost, которая уже имеет эту перегрузку для более низких версий C++, или любую другую реализацию, имеющую эту перегрузку.
Последний вариант, если вы застряли на C++17 и не можете перейти на C++20 или использовать boost или любую другую библиотеку, — это создать собственный контейнер, который хранит std::string_view
в наборе и соответствующий string_view
в ключах карты, поэтому что вы можете использовать find
в качестве аргумента string
... но это расточительно для памяти (дополнительные 16 байт на ключ)
«если где-то нужно использовать std::string, я использую .data() для получения строки». Я не понимаю. Если вам нужен
std::string
, вам нужно использоватьstd::string
, а не что-то еще.