Из сказанного очевидно следует, что стратегия выделения памяти для малых строк может сыграть важную роль, если вы собираетесь работать с большим количеством коротких строк и (1) в вашей рабочей среде не хватает памяти или (2) вы стремитесь по возможности локализовать ссылки и пытаетесь сгруппировать строки в минимальном количестве страниц памяти.
Конечно, в выборе реализации string
разработчик обладает большей степенью свободы, чем кажется на первый взгляд, причем эта свобода используется разными способами. Ниже перечислены ишь некоторые переменные факторы.
• По отношению к содержимому string
может использоваться (или не использоваться) подсчет ссылок. По умолчанию во многих реализациях подсчет ссылок включен, но обычно предоставляется возможность его отключения (как правило, при помощи препроцессорного макроса). В совете 13 приведен пример специфической ситуации, когда может потребоваться отключение подсчета ссылок, но такая необходимость может возникнуть и по другим причинам. Например, подсчет ссылок экономит время лишь при частом копировании строк. Если в приложении строки копируются редко, затраты на подсчет ссылок не оправдываются.
• Объекты string
занимают в 1-7 (по меньшей мере) раз больше памяти, чем указатели char*
.
• Создание нового объекта string
может потребовать нуля, одной или двух операций динамического выделения памяти.
• Объекты string
могут совместно использовать данные о размере и емкости строки.
• Объекты string
могут поддерживать (или не поддерживать) распределители памяти уровня объекта.
• В разных реализациях могут использоваться разные стратегии ограничения размеров выделяемого блока.
Только не поймите меня превратно. Я считаю, что контейнер 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;
Выражение 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]
пытается получить указатель на несуществующий блок памяти с непредсказуемыми последствиями. Нехорошо. Более надежный вариант вызова выглядит так:
Читать дальше