Указывает ли «&s[0]» на смежные символы в std::string?
Вопрос
Я выполняю некоторые работы по техническому обслуживанию и наткнулся на что-то вроде следующего:
std::string s;
s.resize( strLength );
// strLength is a size_t with the length of a C string in it.
memcpy( &s[0], str, strLength );
Я знаю, что использование &s[0] было бы безопасно, если бы это был std::vector, но безопасно ли это использовать std::string?
Решение
В соответствии со стандартом C++98/03 не гарантируется, что выделение std::string будет непрерывным, но C++11 требует этого.На практике ни я, ни Херб Саттер знать реализацию, которая не использует непрерывное хранилище.
Обратите внимание, что &s[0]
В соответствии со стандартом C++11 вещь всегда гарантированно работает, даже в случае строки с нулевой длиной.Это не было бы гарантировано, если бы вы это сделали str.begin()
или &*str.begin()
, но для &s[0]
стандарт определяет operator[]
как:
Возврат:
*(begin() + pos)
еслиpos < size()
, в противном случае ссылка на объект типаT
со стоимостьюcharT()
;указанное значение не должно быть изменено
Продолжая, data()
определяется как:
Возврат: Указатель
p
такой, чтоp + i == &operator[](i)
для каждогоi
в[0,size()]
.
(обратите внимание на квадратные скобки на обоих концах диапазона)
Уведомление:предварительная стандартизация C++0x не гарантировала &s[0]
работать со строками нулевой длины (на самом деле это было явно неопределенное поведение), и это объяснялось в более старой версии этого ответа;это было исправлено в более поздних версиях стандартов, поэтому ответ был соответствующим образом обновлен.
Другие советы
Технически нет, поскольку std::string
не требуется хранить его содержимое в памяти непрерывно.
Однако почти во всех реализациях (каждая из которых мне известна) содержимое хранится последовательно, и это «работает».
Это безопасно использовать.Я думаю, что большинство ответов когда-то были правильными, но стандарт изменился.Цитата из стандарта С++ 11, общие требования Basic_string [string.require], 21.4.1.5, говорит:
Объекты типа char в объекте Basic_string должны храниться последовательно.То есть, для любого объекта basic_string s, идентичность &*(s.begin () + n) == &*s.begin () + n должен удерживать все значения n, такие, что 0 <= n <s.size ().
Чуть раньше там говорилось, что все итераторы являются итераторами произвольного доступа.Оба бита поддерживают использование вашего вопроса.(Кроме того, Страуструп, очевидно, использует его в своей новейшей книге ;))
Не исключено, что это изменение было сделано в C++11.Кажется, я помню, что такая же гарантия была добавлена тогда для вектора, который тоже получил весьма полезную информацию. данные() указатель с этим выпуском.
Надеюсь, это поможет.
Читателям следует отметить, что этот вопрос был задан в 2009 году, когда текущей публикацией был стандарт C++03.Этот ответ основан на той версии Стандарта, в которой std::string
это нет гарантированно использовать непрерывное хранилище.Поскольку этот вопрос не задавался в контексте конкретной платформы (например, gcc), я не делаю никаких предположений о платформе OP - в частности, использовала ли она непрерывное хранилище для хранения данных или нет. string
.
Законно?Может быть, а может и нет.Безопасный?Наверное, а может и нет.Хороший код?Ну, не будем туда...
Почему бы просто не сделать:
std::string s = str;
...или:
std::string s(str);
...или:
std::string s;
std::copy( &str[0], &str[strLen], std::back_inserter(s));
...или:
std::string s;
s.assign( str, strLen );
?
Код может работать, но скорее по счастливой случайности, чем по суждению, он делает предположения о реализации, которые не гарантируются.Я считаю, что определение валидности кода не имеет значения, поскольку это бессмысленное усложнение, которое легко сводится к следующему:
std::string s( str ) ;
или при назначении существующему объекту std::string просто:
s = str ;
а затем пусть std::string сам определит, как добиться результата.Если вы собираетесь прибегнуть к подобной ерунде, то вы можете также не использовать std::string и придерживаться его, поскольку вы вновь представляете все опасности, связанные со строками C.
Это вообще нет безопасно, независимо от того, хранится ли внутренняя последовательность строк в памяти постоянно или нет.Может быть много других деталей реализации, связанных с тем, как контролируемая последовательность хранится std::string
объект, помимо непрерывности.
Реальная практическая проблема с этим может заключаться в следующем.Контролируемая последовательность std::string
не требуется хранить в виде строки с нулевым завершением.Однако на практике многие (большинство?) реализаций предпочитают увеличивать размер внутреннего буфера на 1 и в любом случае сохранять последовательность как строку с нулевым завершением, поскольку это упрощает реализацию c_str()
метод:просто верните указатель на внутренний буфер, и все готово.
Код, который вы указали в своем вопросе, не предпринимает никаких усилий для завершения копирования данных во внутренний буфер.Вполне возможно, что он просто не знает, необходимо ли нулевое завершение для данной реализации std::string
.Вполне возможно, что это связано с заполнением внутреннего буфера нулями после вызова resize
, поэтому дополнительный символ, выделенный реализацией для нулевого терминатора, удобно предварительно установить на ноль.Все это детали реализации, а это означает, что данный метод зависит от некоторых довольно хрупких предположений.
Другими словами, в некоторых реализациях вам, вероятно, придется использовать strcpy
, нет memcpy
чтобы принудительно привести данные в контролируемую последовательность вот так.Хотя в некоторых других реализациях вам придется использовать memcpy
и не strcpy
.