Ответ 1
Фон/Обзор
Операции с автоматическими переменными ( "из стека", которые вы создаете без вызова malloc
/new
), обычно намного быстрее, чем операции с бесплатным хранилищем ( "куча", которые являются переменными, которые создаются с помощью new
). Тем не менее, размер автоматических массивов фиксируется во время компиляции, но размер массивов из бесплатного хранилища не является. Более того, размер стека ограничен (как правило, несколько MiB), тогда как свободное хранилище ограничено только вашей системной памятью.
SSO - это оптимизация коротких/малых строк. A std::string
обычно сохраняет строку как указатель на свободное хранилище ( "куча" ), что дает аналогичные характеристики производительности, как если бы вы звонили new char [size]
. Это предотвращает переполнение стека для очень больших строк, но оно может быть медленнее, особенно при копировании. В качестве оптимизации многие реализации std::string
создают небольшой автоматический массив, что-то вроде char [20]
. Если у вас есть строка, длина которой составляет 20 символов или меньше (в данном примере изменяется фактический размер), он хранит ее непосредственно в этом массиве. Это позволяет избежать вызова new
вообще, что немного ускоряет работу.
EDIT:
Я не ожидал, что этот ответ будет настолько популярен, но, поскольку это так, позвольте мне дать более реалистичную реализацию, с оговоркой, что я никогда не читал никакой реализации SSO "в дикой природе".
Сведения о реализации
Как минимум, a std::string
необходимо сохранить следующую информацию:
- Размер
- Емкость
- Местоположение данных
Размер может быть сохранен как std::string::size_type
или как указатель на конец. Единственное различие заключается в том, хотите ли вы вычесть два указателя, когда пользователь вызывает size
или добавляет size_type
к указателю, когда пользователь вызывает end
. Емкость также может быть сохранена в любом случае.
Вы не платите за то, что не используете.
Сначала рассмотрим наивную реализацию, основанную на том, что я изложил выше:
class string {
public:
// all 83 member functions
private:
std::unique_ptr<char[]> m_data;
size_type m_size;
size_type m_capacity;
std::array<char, 16> m_sso;
};
Для 64-разрядной системы это обычно означает, что std::string
имеет 24 байта "служебных" данных для каждой строки, плюс еще 16 для буфера SSO (16 выбрано здесь вместо 20 из-за требований дополнения). Было бы бесполезно хранить эти три элемента данных плюс локальный массив символов, как в моем упрощенном примере. Если m_size <= 16
, тогда я поместил все данные в m_sso
, поэтому я уже знаю емкость, и мне не нужен указатель на данные. Если m_size > 16
, то мне не нужно m_sso
. Нет абсолютно никакого перекрытия, где мне все они нужны. Более разумное решение, которое не тратило бы пространство, выглядело бы чем-то более похожим на это (непроверенные, только примерные цели):
class string {
public:
// all 83 member functions
private:
size_type m_size;
union {
class {
// This is probably better designed as an array-like class
std::unique_ptr<char[]> m_data;
size_type m_capacity;
} m_large;
std::array<char, sizeof(m_large)> m_small;
};
};
Я бы предположил, что большинство реализаций больше похожи на это.