В С++ 11 какой самый эффективный способ вернуть ссылку/указатель на позицию в std::string?

Я создаю анализатор текста, который использует std::string в качестве основного хранилища для строк.

Я знаю, что это не оптимально, и что синтаксические анализаторы внутри компиляторов используют для этого оптимизированные подходы. В моем проекте я не возражаю против потери производительности в обмен на большую ясность и простоту обслуживания.

Сначала я читаю в память огромный текст, а затем сканирую каждый символ, чтобы построить упорядоченный набор токенов, это простой лексер. В настоящее время я использую std::string для представления текста токена, но я хотел бы немного улучшить это, используя ссылку/указатель на исходный текст.

Из того, что я прочитал, возврат и удержание итераторов является плохой практикой, а также плохой практикой является обращение к внутреннему буферу std::string.

Любые предложения о том, как выполнить это "чистым" способом?


person Pedro Salgueiro    schedule 15.07.2014    source источник
comment
Что касается итераторов, это неплохая практика, если вы убедитесь, что они не признаны недействительными и не используются.   -  person Marco A.    schedule 15.07.2014
comment
Если вы все равно храните весь файл в памяти, почему бы просто не записать его в память? Вероятно, будет более эффективным.   -  person Deduplicator    schedule 15.07.2014
comment
Я, вероятно, упускаю что-то очевидное, но не могли бы вы просто вернуть целое число, представляющее смещение байта токена от начала строки? ints довольно эффективны...   -  person Jeremy Friesner    schedule 15.07.2014
comment
Взгляните на string_view.   -  person Casey    schedule 15.07.2014
comment
У меня была неправильная идея, что я не должен возвращать итераторы вызывающим объектам вне области действия класса, которому принадлежит строка. В моем случае они будут действительны до конца программы. В этом случае я бы сказал, что итератор — хорошая замена целому числу из-за абстракции.   -  person Pedro Salgueiro    schedule 15.07.2014


Ответы (4)


Есть предложения добавить string_view в C++ в будущем стандарте.

string_view — это итерируемый диапазон символов без владения со многими утилитами и свойствами, которые вы ожидаете от строкового класса, за исключением того, что вы не можете вставлять/удалять символы (и редактирование символов часто блокируется в некоторых подтипах).

Я бы посоветовал попробовать этот подход - написать свой собственный (в своем собственном пространстве имен утилит). (В любом случае у вас должно быть собственное пространство имен утилит для повторно используемых фрагментов кода).

Данные ядра представляют собой пару char* pr std::string::iterators (или const версий). Если пользователю нужен буфер с нулевым завершением, метод to_string выделяет его. Я бы начал с неизменяемых (const) символьных данных. Не забывайте begin и end: это делает ваше представление итерируемым с for(:) циклами.

У этого дизайна есть опасность, что исходный std::string должен сохраняться достаточно долго, чтобы пережить все представления.

Если вы готовы пожертвовать некоторой производительностью ради безопасности, создайте представление, владеющее std::shared_ptr<const std::string>, в которое оно может переместить std::string, и в качестве первого шага переместите в него весь буфер, а затем начните разбивать/разбирать его. (дочерние представления создают новый общий указатель на те же данные). Тогда ваш класс представления больше похож на неизменяемую строку с общим хранилищем.

К преимуществам версии shared_ptr<const> относятся безопасность, более длительное время жизни представлений (больше нет зависимости от времени жизни), и вы можете легко перенаправить свои методы типа "подстрока" const в std::string, чтобы писать меньше кода.

К недостаткам можно отнести возможную несовместимость с поступающим стандартным1 и более низкую производительность из-за перетаскивания shared_ptr.

Я подозреваю, что представления и диапазоны будут играть все более важную роль в современном C++ с предстоящими и недавними улучшениями языка.

boost::string_ref, по-видимому, является реализацией предложение к стандарту C++1y.


1 однако, учитывая простоту добавления возможностей в метапрограммирование шаблонов, использование аргумента шаблона "владелец ресурса" для типа представления может быть хорошим дизайнерским решением. Тогда у вас могут быть владеющие и не владеющие string_view с одинаковой семантикой...

person Yakk - Adam Nevraumont    schedule 15.07.2014
comment
Швы string_view выглядят как отличное начало для сохранения начальных и конечных итераторов в моем тексте токена. Спасибо! - person Pedro Salgueiro; 15.07.2014
comment
В boost это уже реализовано boost.org/doc /libs/1_55_0/libs/utility/doc/html/string_ref.html - person NoSenseEtAl; 16.07.2014

Некоторые здесь:

-Внутреннее представление строки живет в то же время, что и сама строка, если вы сохраните указатель или итераторы на строку для использования последнего (например, отчеты о печати, постобработка и т. д.) в области действия строки, вы столкнетесь с недопустимой памятью доступ. Обычно в этом типе обработки текст активен все время процесса.
-Итераторы — хороший выбор (для максимальной производительности и универсальности я предлагаю использовать константный необработанный указатель const char*, потому что источником может быть почти что угодно, строка, буфер, сопоставленный буфер, считанные данные из потока и т. д.)
 – Рекомендуется вместо копирования токенов сохранить пару (итератор начала токена, итератор конца токена) в наборе токенов.
- Крайне важно для производительности стараться не делать много аллокаций (alloc - одна из самых дорогих операций на любом языке)

Вы можете проверить lexertl (для получения дополнительных идей или для его использования): http://www.benhanson.net/lexertl.html и дух (более полный): http://www.boost.org/doc/libs/release/libs/spirit/

person NetVipeC    schedule 15.07.2014

Возврат и использование итераторов — неплохая практика. Конечно, если предположить, что вы не изменяете входной буфер, но это не похоже на то, что вы делаете.

person Wojtek Surowka    schedule 15.07.2014

Я могу считаться здесь язычником, но пока вы работаете над const reference фактическим string, я не вижу причин не использовать const char* в данных строки (пока вы используете c++ 11).

В соответствии с стандартом c++11 внутренние данные std::string должны быть непрерывными, и никакие указатели не могут быть признаны недействительными, если только строка не подвергается обработке по неконстантной ссылке.

21.4.1 общие требования basic_string

5 Символоподобные объекты в объекте basic_string должны храниться непрерывно. То есть для любого объекта basic_string s тождество &*(s.begin() + n) == &*s.begin() + n должно выполняться для всех значений n, таких что 0 ‹= n ‹ s.size ().

6 Ссылки, указатели и итераторы, относящиеся к элементам последовательности basic_string, могут быть признаны недействительными в следующих случаях использования этого объекта basic_string:

в качестве аргумента любой стандартной библиотечной функции, принимающей в качестве аргумента ссылку на неконстантную базовую_строку.

Вызов неконстантных функций-членов, кроме operator[], at, front, back, begin, rbegin, end и rend.

Поэтому вместо использования s.data() используйте &s.begin(), чтобы получить фактический внутренний буфер.

ПРИМЕЧАНИЕ. Я почти уверен, что эти гарантии не распространяются на предыдущие версии стандарта.

person Galik    schedule 15.07.2014
comment
Как вы думаете, почему s.data() не наследует эти гарантии? (с дополнительным преимуществом определенного поведения для size()==0) - person Yakk - Adam Nevraumont; 15.07.2014
comment
@Yakk В С++ 11 s.data() вполне может иметь такие же гарантии. Я недостаточно знаком с ним, чтобы быть уверенным. Но, насколько я понимаю, предыдущая версия стандарта не требовала, чтобы внутреннее представление std::string было непрерывным или заканчивалось нулем. Но строка, возвращенная из s.c_str(), была. Это означало, что реализация могла предоставить вам копию реальных данных, удобную для c-строки. И s.data() является синонимом s.c_str(). - person Galik; 15.07.2014
comment
Это работает только в том случае, если вы декодируете строку в utf-16 или utf-32, иначе арифметика указателя не будет работать (увеличение указателя на единицу может не означать увеличение на единицу индекса char). - person Pedro Salgueiro; 16.07.2014
comment
@Pedro Использование const char* работает так же, как доступ к std::string через std::string::iterator или operator[]. Ни один из обычных строковых методов не будет работать в Юникоде, если строка заполнена данными Юникода. Даже s.size(); не скажет вам, сколько символов в строке. Библиотеки, предназначенные для обработки данных Unicode, скорее всего, будут отлично работать с const char*, или же они предоставят свои собственные итераторы, которые будут работать. - person Galik; 16.07.2014