Только не поймите меня превратно. Я считаю, что контейнер string
является одним из важнейших компонентов стандартной библиотеки и рекомендую использовать его как можно чаще. Например, совет 13 посвящен возможности использования string
вместо динамических символьных массивов. Но для эффективного использования STL необходимо разбираться во всем разнообразии реализаций string
, особенно если ваша программа должна работать на разных платформах STL при жестких требованиях к быстродействию.
Кроме того, на концептуальном уровне контейнер string
выглядел предельно просто. Кто бы мог подумать, что его реализация таит столько неожиданностей?
Совет 16. Научитесь передавать данные vector и string функциям унаследованного интерфейса
С момента стандартизации C++ в 1998 году элита C++ настойчиво подталкивает программистов к переходу с массивов на vector. Столь же открыто пропагандируется переход от указателей char*
к объектам string
. В пользу перехода имеются достаточно веские аргументы, в том числе ликвидация распространенных ошибок программирования (совет 13) и возможность полноценного использования всей мощи алгоритмов STL (совет 31).
Но на этом пути остаются некоторые препятствия, из которых едва ли не самым распространенным являются унаследованные интерфейсы языка C, работающие с массивами и указателями char*
вместо объектов vector
и string
. Они существуют с давних времен, и если мы хотим эффективно использовать STL, придется как-то уживаться с этими «пережитками прошлого».
К счастью, задача решается просто. Если у вас имеется vector v
и вы хотите получить указатель на данные v
, которые интерпретировались бы как массив, воспользуйтесь записью &v[0]
. Для string s
аналогичная запись имеет вид s.c_str
. Впрочем, это не все — существуют некоторые ограничения (то, о чем в рекламе обычно пишется самым мелким шрифтом).
Рассмотрим следующее объявление:
vector
Выражение v[0]
дает ссылку на первый элемент вектора, соответственно &v[0]
— указатель на первый элемент. В соответствии со Стандартом C++ элементы vector
должны храниться в памяти непрерывно, по аналогии с массивом. Допустим, у нас имеется функция C, объявленная следующим образом:
void doSomething(const int* pInts, size_t numlnts);
Передача данных должна происходить так:
doSomething(&v[0], v.size);
Во всяком случае, так v
пуст. В этом случае функция v.size
вернет 0, а &v[0]
пытается получить указатель на несуществующий блок памяти с непредсказуемыми последствиями. Нехорошо. Более надежный вариант вызова выглядит так:
if (!v.empty) {
doSomething(&v[0], v.size);
}
Отдельные подозрительные личности утверждают, что &v[0]
можно заменить на v.begin
, поскольку begin
возвращает итератор, а для vector
итератор в действительности представляет собой указатель. Во многих случаях это действительно так, но, как будет показано в совете 50, это правило соблюдается не всегда, и полагаться на него не стоит. Функция begin
возвращает итератор, а не указатель, поэтому она никогда не должна использоваться для получения указателя на данные vector
. А если уж вам очень приглянулась запись v.begin
, используйте конструкцию &*v.begin
— она вернет тот же указатель, что и &v[0]
, хотя это увеличивает количество вводимых символов и затрудняет работу людей, пытающихся разобраться в вашей программе. Если знакомые вам советуют использовать v.begin
вместо &v[0]
— лучше смените круг общения.
Способ получения указателя на данные контейнера, хорошо работающий для vector
, недостаточно надежен для string
. Во-первых, контейнер string
не гарантирует хранения данных в непрерывном блоке памяти; во-вторых, внутреннее представление строки не обязательно завершается нуль-символом. По этим причинам в контейнере string предусмотрена функция c_str
, которая возвращает указатель на содержимое строки в формате C. Таким образом, передача строки s
функции
void doSomething(const char *pString);
происходит так:
doSomething(s.c_str);
Данное решение подходит и для строк нулевой длины. В этом случае c_str
возвращает указатель на нуль-символ. Кроме того, оно годится и для строк с внутренними нуль-символами, хотя в этом случае doSomething
с большой вероятностью интерпретирует первый внутренний нуль-символ как признак конца строки. Присутствие внутренних нуль-символов несущественно для объектов string
, но не для функций C, использующих char*
.
Вернемся к объявлениям doSomething:
void doSomething(const int* pints, size_t numInts);
void doSomething(const char *pString);