По сравнению с дешевой стоимостью std::string_view
, как указано, это добавило бы много накладных расходов на то, что большинству людей не нужно. Вызов функций, изменяющих размер строки, делает недействительными все указатели, ссылки и итераторы для этой строки, поскольку может потребоваться перераспределение и перемещение данных в новое место. Вы можете обойти это, используя индексы, а не указатели, но это не бесплатно и, как уже упоминалось, даже не дешево по сравнению с базовой стоимостью этой облегченной абстракции.
Чтобы продемонстрировать, что я имею в виду, рассмотрим эту минимизированную версию как могла бы выглядеть такая реализация ( живой пример ):
class stable_string_view {
const char*(*get_data_start)(const void*);
// Highly recommended for sanity: std::size_t(*get_data_size)(const void*);
const void* data_source;
std::size_t first, last;
public:
stable_string_view(const std::string& str) noexcept
: get_data_start{[](const void* source) { return static_cast<const std::string*>(source)->data(); }},
data_source{&str},
first{0}, last{str.size()} {}
stable_string_view(const char* cstr) noexcept
: get_data_start{[](const void* source) { return static_cast<const char*>(source); }},
data_source(cstr),
first{0}, last{std::strlen(cstr)} {}
auto size() const noexcept -> std::size_t {
return last - first;
}
auto operator[](std::size_t index) const -> const char& {
return get_data_start(data_source)[first + index];
}
auto substr(std::size_t pos = 0, std::size_t count = -1) const -> stable_string_view {
if (pos > size()) {
// Removed: This piece of code would distract from the basic answer.
}
auto rcount = std::min(count, size() - pos);
auto copy = *this;
copy.first += pos;
copy.last = copy.first + rcount;
return copy;
}
void output() const {
auto data = get_data_start(data_source);
for (auto i = first; i < last; ++i) {
std::putchar(data[i]);
}
std::putchar('\n');
}
};
Первое, что должно броситься в глаза, это:
const char*(*get_data_start)(const void*);
What это точно? Это примерно минимум, который нам нужен для того, чтобы иметь возможность индексировать исходные данные. Вызов этого происходит и получает указатель fre sh. На самом деле мы только что увеличили размер каждого отдельного объекта на 50%. Это означает, что нужно копировать на 50% больше и меньше места в кеше, если их много (например, синтаксический анализатор, сохраняющий представления для исходного текста файла). В этой реализации это 100%, потому что есть и указатель на функцию, и непрозрачный указатель.
Мы всегда можем поместить несколько вещей за указатель, чтобы уменьшить размер, но это наверняка еще одна стоимость времени выполнения. На данный момент легкая абстракция уже не такая легкая. И это даже без какой-либо проверки работоспособности (которую вам действительно, действительно нужно иметь, если вы собираетесь изменять размер источника данных, пока эта штука все еще существует), но для этого требуется другой размер и / или снижение производительности.
Конечно, это всего лишь хит размера, верно? Неправильно. Я собрал базовое c сравнение между std::string_view
и этим:
char with_stable(stable_string_view view) {
return view.substr(5, 8)[3];
}
char with_standard(std::string_view view) {
return view.substr(5, 8)[3];
}
with_stable(stable_string_view): # @with_stable(stable_string_view)
push rbx
mov rbx, qword ptr [rsp + 32]
mov rdx, qword ptr [rsp + 40]
sub rdx, rbx
cmp rdx, 4
jbe .LBB0_2
lea rax, [rsp + 16]
mov rdi, qword ptr [rax + 8]
call qword ptr [rax]
mov al, byte ptr [rbx + rax + 8]
pop rbx
ret
.LBB0_2:
mov edi, offset .L.str.2
mov esi, 5
xor eax, eax
call std::__throw_out_of_range_fmt(char const*, ...)
with_standard(std::basic_string_view >): # @with_standard(std::basic_string_view >)
push rax
cmp rdi, 4
jbe .LBB1_2
mov al, byte ptr [rsi + 8]
pop rcx
ret
.LBB1_2:
mov rcx, rdi
mov edi, offset .L.str.3
mov esi, offset .L.str.2
mov edx, 5
xor eax, eax
call std::__throw_out_of_range_fmt(char const*, ...)
Теперь часть этого - обработка исключений, которую я пытался сделать так же близко, как и я. может на код with_standard
для упрощения сравнения. На самом деле важная строка:
call qword ptr [rax]
Это другая цена за производительность указателя на функцию в классе. Компилятор не всегда может видеть насквозь, поэтому такая простая задача, как получение начала данных, может быть намного дороже, чем должно быть. Вы можете немного сократить это, сохранив вместо этого const std::string*
, но это нарушит цель string_view
, которая работает с любым непрерывным диапазоном символов. Хотя, возможно, это подойдет вашим потребностям.
В заключение, вполне возможно сохранить общее c строковое представление, которое остается действительным через аннулирование итератора. Однако это требует больше места и / или дополнительных затрат времени выполнения для чего-то, что должно быть типом словаря в API. Давать людям вескую причину избегать использования типов словаря, когда им не нужна дополнительная гарантия, не кажется хорошей идеей. Это может быть родственный класс, но я не заметил спроса на него. С этим нужно было бы разобраться при подготовке к написанию предложения.