Histórico / Visão Geral
Operações em variáveis automáticas ("da pilha", que são criadas sem chamar malloc
/ new
) geralmente são muito mais rápidas do que aquelas que envolvem o armazenamento gratuito ("a pilha", que são criadas usando new
). No entanto, o tamanho das matrizes automáticas é fixo no momento da compilação, mas o tamanho das matrizes do armazenamento gratuito não é. Além disso, o tamanho da pilha é limitado (normalmente alguns MiB), enquanto o armazenamento gratuito é limitado apenas pela memória do seu sistema.
SSO é a otimização de cadeia curta / pequena. A std::string
normalmente armazena a string como um ponteiro para a loja gratuita ("a pilha"), o que fornece características de desempenho semelhantes às de uma chamada new char [size]
. Isso evita um estouro de pilha para cadeias muito grandes, mas pode ser mais lento, especialmente com operações de cópia. Como otimização, muitas implementações std::string
criam uma pequena matriz automática, algo assim char [20]
. Se você tiver uma string com 20 caracteres ou menos (dado este exemplo, o tamanho real varia), ela será armazenada diretamente nessa matriz. Isso evita a necessidade de ligar new
, o que acelera um pouco as coisas.
EDITAR:
Eu não esperava que essa resposta fosse tão popular, mas, como é, permita-me dar uma implementação mais realista, com a ressalva de que nunca realmente li nenhuma implementação do SSO "in the wild".
Detalhes da implementação
No mínimo, é std::string
necessário armazenar as seguintes informações:
- O tamanho
- A capacidade
- A localização dos dados
O tamanho pode ser armazenado como um std::string::size_type
ou como um ponteiro para o final. A única diferença é se você deseja subtrair dois ponteiros quando o usuário chama size
ou adicionar um size_type
a um ponteiro quando o usuário chama end
. A capacidade também pode ser armazenada de qualquer maneira.
Você não paga pelo que não usa.
Primeiro, considere a implementação ingênua com base no que descrevi acima:
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;
};
Para um sistema de 64 bits, isso geralmente significa que std::string
possui 24 bytes de 'sobrecarga' por sequência, além de outros 16 para o buffer SSO (16 escolhidos aqui em vez de 20 devido a requisitos de preenchimento). Realmente não faria sentido armazenar esses três membros de dados mais uma matriz local de caracteres, como no meu exemplo simplificado. Se m_size <= 16
, então, colocarei todos os dados m_sso
, para que eu já conheça a capacidade e não precise do ponteiro para os dados. Se m_size > 16
, então eu não preciso m_sso
. Não há absolutamente nenhuma sobreposição onde eu preciso de todos eles. Uma solução mais inteligente que não desperdice espaço seria algo mais ou menos assim (apenas para fins de exemplo não testados):
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;
};
};
Eu diria que a maioria das implementações se parece mais com isso.
std::string
implementada", e uma outra pergunta: "o que faz SSO média", você tem que ser absolutamente insano considerar que eles sejam a mesma pergunta