использование std :: string_view не ясно, но viewable_area - PullRequest
0 голосов
/ 20 июня 2020
• 1000 ** 1001 string_view obj хочет, чтобы немодифицируемая строка была представлена ​​для всего живого, запутавшись:

...

auto sub[] = (const std::string &s) -> std::string_view { return std::move( std::string_view(s).substr(6,5) )};

...

string s("Hello world");
auto f = sub( s );

// I'm suspect that string_view should attaches to indices of original string

std::cout << f; // OK

// lets modify original expecting our string_view f will move it's begin and end to 5 symbols front 
s.insert(0, "shift"); 

std::cout << f; // Fail: corrupted memory 

Я понимаю, что если я изменю видимую область часть, или будут очищены или удалены et c все в порядке. но почему, если модификация происходит как: вставка / стирание в "s", это сделает мою видимую часть "f" недействительной?

Есть ли какие-либо другие классы / адаптеры, которые C ++ 20 может дать мне, чтобы сделать это, как я увидеть?

Ответы [ 2 ]

4 голосов
/ 20 июня 2020

По сравнению с дешевой стоимостью 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. Давать людям вескую причину избегать использования типов словаря, когда им не нужна дополнительная гарантия, не кажется хорошей идеей. Это может быть родственный класс, но я не заметил спроса на него. С этим нужно было бы разобраться при подготовке к написанию предложения.

1 голос
/ 20 июня 2020

Я проверяю новые функции в C ++ 20, и вот класс std :: string_view.

Обратите внимание, что string_view был введен еще в C ++ 17.

Почему, если модификация происходит как: вставка / стирание в «s», это приведет к аннулированию моей видимой части «f»?

Потому что так указывается std :: string. Некоторые операции делают недействительными все ссылки на строку. Строковое представление - это ссылка на строку. Такой операцией является увеличение размера строки за пределами ее емкости. *

Если вам нужна подстрока, которая не становится недействительной, когда исходная строка становится недействительной, то для хранения подстроки вы можете использовать std :: string.

...