Foi meu entendimento que copy-on-write não é uma maneira viável de implementar uma conformidade std::string
em C ++ 11, mas quando surgiu em discussão recentemente, descobri que não era possível apoiar diretamente essa declaração.
Estou correto que C ++ 11 não admite implementações baseadas em COW std::string
?
Em caso afirmativo, essa restrição está explicitamente declarada em algum lugar do novo padrão (onde)?
Ou essa restrição está implícita, no sentido de que é o efeito combinado dos novos requisitos sobre o std::string
que impede uma implementação baseada em COW std::string
. Neste caso, eu estaria interessado em uma derivação de estilo de capítulo e verso de 'C ++ 11 efetivamente proíbe std::string
implementações baseadas em COW '.
Respostas:
Não é permitido, porque de acordo com o padrão 21.4.1 p6, a invalidação de iteradores / referências só é permitida para
Para uma string COW, chamar non-const
operator[]
exigiria fazer uma cópia (e invalidar as referências), o que não é permitido pelo parágrafo acima. Portanto, não é mais legal ter uma string COW em C ++ 11.fonte
std::string a("something"); char& c1 = a[0]; std::string b(a); char& c2 = a[1];
c1 é uma referência a a. Você então "copia" a. Em seguida, quando você tenta obter a referência pela segunda vez, é necessário fazer uma cópia para obter uma referência não const, pois há duas strings que apontam para o mesmo buffer. Isso teria que invalidar a primeira referência feita e vai contra a seção citada acima.operator[]
(1) deve fazer uma cópia e que (2) é ilegal fazê-lo. De qual desses dois pontos você discorda? Olhando para seu primeiro comentário, parece que uma implementação poderia compartilhar a string, pelo menos sob esse requisito, até o momento em que ela seja acessada, mas os acessos de leitura e gravação precisariam cancelar o compartilhamento. É esse o seu raciocínio?As respostas de Dave S e gbjbaanb estão corretas . (E a de Luc Danton também está correta, embora seja mais um efeito colateral da proibição das strings COW do que a regra original que proíbe isso.)
Mas, para esclarecer alguma confusão, vou adicionar mais algumas exposições. Vários comentários vinculam a um comentário meu no bugzilla GCC que dá o seguinte exemplo:
O objetivo desse exemplo é demonstrar por que a string de contagem de referência (COW) do GCC não é válida em C ++ 11. O padrão C ++ 11 requer que esse código funcione corretamente. Nada no código permite que o
p
seja invalidado em C ++ 11.Usando a
std::string
implementação de contagem de referência antiga do GCC , esse código tem comportamento indefinido, porquep
é invalidado, tornando-se um ponteiro pendente. (O que acontece é que, quandos2
é construído, ele compartilha os dadoss
, mas obter uma referência não const vias[0]
requer que os dados sejam descompartilhados, o mesmos
acontece com uma "cópia na gravação" porque a referências[0]
poderia potencialmente ser usada para gravars
, entãos2
vai fora do escopo, destruindo a matriz apontada porp
).O padrão C ++ 03 permite explicitamente esse comportamento em 21.3 [lib.basic.string] p5, onde diz que após uma chamada para
data()
a primeira chamada paraoperator[]()
pode invalidar ponteiros, referências e iteradores. Portanto, a string COW do GCC era uma implementação válida do C ++ 03.O padrão C ++ 11 não permite mais esse comportamento, porque nenhuma chamada para
operator[]()
pode invalidar ponteiros, referências ou iteradores, independentemente de seguirem uma chamada paradata()
.Portanto, o exemplo acima deve funcionar em C ++ 11, mas não funciona com o tipo de string COW de libstdc ++, portanto, esse tipo de string COW não é permitido em C ++ 11.
fonte
.data()
(e em cada retorno de ponteiro, referência ou iterador) não sofre desse problema. Ie (invariante) um buffer é a qualquer momento não compartilhado, ou então compartilhado sem refs externos. Achei que você pretendia fazer o comentário sobre este exemplo como um relatório informal de bug como um comentário, sinto muito por entendê-lo mal! Mas, como você pode ver, considerando a implementação que descrevo aqui, que funciona bem em C ++ 11 quando osnoexcept
requisitos são ignorados, o exemplo não diz nada sobre o formal. Posso fornecer o código se você quiser.std::string
, e eu sinceramente duvido que você possa demonstrar uma string COW útil e de desempenho que atenda aos requisitos de invalidação do C ++ 11. Portanto, mantenho que asnoexcept
especificações que foram adicionadas no último minuto são uma consequência da proibição das strings COW, não o motivo subjacente. N2668 parece perfeitamente claro, por que você continua a negar a evidência clara da intenção do comitê delineada lá?data()
é uma função de membro const, portanto, deve ser seguro chamar simultaneamente com outros membros const e, por exemplo, chamardata()
simultaneamente com outro thread fazendo uma cópia da string. Então você vai precisar de toda a sobrecarga de um mutex para cada operação de string, mesmo const uns, ou a complexidade de uma estrutura de contagem de referência mutável sem bloqueio e, afinal de contas, você só terá compartilhamento se nunca modificar ou acessar suas strings, tantas, muitas strings terão uma contagem de referência de um. Forneça o código, fique à vontade para ignorar asnoexcept
garantias.basic_string
funções-membro, além de funções livres. Custo de abstração: este código de versão zeroth fresco não otimizado é 50 a 100% mais lento com g ++ e MSVC. Ele não oferece segurança de thread (aproveitamento fácilshared_ptr
, eu acho) e é apenas o suficiente para dar suporte à classificação de um dicionário para fins de tempo, mas o módulo bugs prova que uma referência contadabasic_string
é permitida, exceto paranoexcept
requisitos de C ++ . github.com/alfps/In-principle-demo-of-ref-counted-basic_stringÉ, CoW é um mecanismo aceitável para fazer cordas mais rápidas ... mas ...
torna o código multithreading mais lento (todo aquele bloqueio para verificar se você é o único a escrever mata o desempenho ao usar muitas strings). Esta foi a principal razão pela qual CoW foi morto anos atrás.
Os outros motivos são que o
[]
operador irá retornar os dados da string, sem qualquer proteção para você sobrescrever uma string que outra pessoa espera que não seja alterada. O mesmo se aplica ac_str()
edata()
.O Google rápido diz que o multithreading é basicamente a razão pela qual foi efetivamente desabilitado (não explicitamente).
A proposta diz:
Seguido por
As cordas fazem parte do STLPort e SGIs STL.
fonte
De 21.4.2 construtores de basic_string e operadores de atribuição [string.cons]
A Tabela 64 documenta de forma útil que, após a construção de um objeto por meio deste (cópia) construtor,
this->data()
tem como valor:Existem requisitos semelhantes para outros construtores semelhantes.
fonte
Uma vez que agora é garantido que as strings são armazenadas de forma contígua e agora você tem permissão para levar um ponteiro para o armazenamento interno de uma string (ou seja, & str [0] funciona como faria para uma matriz), não é possível fazer um COW útil implementação. Você teria que fazer uma cópia para muitas coisas. Mesmo usando apenas
operator[]
oubegin()
em uma string não const, seria necessária uma cópia.fonte
c_str()
) deve ser O (1) e não pode lançar, e não deve introduzir corridas de dados, então é muito difícil atender a esses requisitos se você concatenar preguiçosamente. Na prática, a única opção razoável é sempre armazenar dados contíguos.O COW é
basic_string
proibido em C ++ 11 e posterior?A respeito de
Sim.
A respeito de
Quase diretamente, por requisitos de complexidade constante para uma série de operações que exigiriam O ( n ) cópia física dos dados da string em uma implementação COW.
Por exemplo, para as funções de membro
... que em uma implementação de COW, ambos acionariam a cópia dos dados da string para não compartilhar o valor da string, o padrão C ++ 11 requer
C ++ 11 §21.4.5 / 4 :… Que exclui a cópia de dados e, portanto, COW.
C ++ 03 suportado implementações da vaca por não ter esses requisitos constantes complexidade, e, sob certas condições restritivas, permitindo chamadas para
operator[]()
,at()
,begin()
,rbegin()
,end()
, ourend()
para referências invalidar os ponteiros e iterators referentes aos itens corda, ou seja, possivelmente incorrer em uma Cópia de dados COW. Este suporte foi removido no C ++ 11.O COW também é proibido por meio das regras de invalidação do C ++ 11?
Em outra resposta que no momento da redação foi selecionada como solução, e que foi fortemente votada e, portanto, aparentemente acreditada, afirma-se que
Essa afirmação é incorreta e enganosa de duas maneiras principais:
const
acessadores de não itens precisam acionar uma cópia de dados COW.Mas também os
const
acessadores de item precisam acionar a cópia de dados, porque eles permitem que o código do cliente forme referências ou ponteiros que (em C ++ 11) não é permitido invalidar posteriormente por meio das operações que podem acionar a cópia de dados COW.Mas, em uma implementação correta, a cópia de dados COW, não compartilhando o valor da string, é feita em um ponto antes que haja qualquer referência que possa ser invalidada.
Para ver como uma implementação COW correta do C ++ 11
basic_string
funcionaria, quando os requisitos O (1) que tornam isso inválido são ignorados, pense em uma implementação em que uma string pode alternar entre as políticas de propriedade. Uma instância de string começa com política compartilhável. Com esta política ativa, não pode haver referências de itens externos. A instância pode fazer a transição para a política Exclusiva e deve fazer isso quando uma referência de item é potencialmente criada, como com uma chamada para.c_str()
(pelo menos se isso produzir um ponteiro para o buffer interno). No caso geral de várias instâncias compartilhando a propriedade do valor, isso envolve a cópia dos dados da string. Após essa transição para a política Exclusiva, a instância só pode fazer a transição de volta para Compartilhável por uma operação que invalida todas as referências, como atribuição.Portanto, embora a conclusão dessa resposta, de que as sequências de COW estão descartadas, seja correta, o raciocínio oferecido é incorreto e fortemente enganoso.
Suspeito que a causa desse mal-entendido seja uma nota não normativa no anexo C do C ++ 11:
C ++ 11 §C.2.11 [diff.cpp03.strings], sobre §21.3:Aqui, a justificativa explica o principal motivo pelo qual alguém decidiu remover o suporte COW especial do C ++ 03. Este raciocínio, o porquê , não é como o padrão efetivamente desautoriza a implementação de COW. O padrão não permite COW através dos requisitos O (1).
Resumindo, as regras de invalidação do C ++ 11 não excluem uma implementação COW de
C ++ 03 §21.3 / 5, que inclui suporte COW de "primeira chamada":std::basic_string
. Mas eles descartam uma implementação COW irrestrita no estilo C ++ 03, razoavelmente eficiente, como aquela em pelo menos uma das implementações de biblioteca padrão do g ++. O suporte COW especial C ++ 03 permitiu eficiência prática, em particular usandoconst
acessores de item, ao custo de regras sutis e complexas para invalidação:Essas regras são tão complexas e sutis que duvido que muitos programadores, se houver, possam fornecer um resumo preciso. Eu não pude.
E se os requisitos O (1) forem desconsiderados?
Se os requisitos de tempo constante do C ++ 11 em, por exemplo,
operator[]
forem desconsiderados, o COW parabasic_string
poderia ser tecnicamente viável, mas difícil de implementar.As operações que podem acessar o conteúdo de uma string sem incorrer na cópia de dados COW incluem:
+
.<<
.basic_string
como argumento para funções de biblioteca padrão.Este último porque a biblioteca padrão pode contar com conhecimentos e construções específicas de implementação.
Além disso, uma implementação pode oferecer várias funções não padronizadas para acessar o conteúdo da string sem acionar a cópia de dados COW.
Um fator complicador principal é que no C ++ 11 o
basic_string
acesso ao item deve acionar a cópia dos dados (cancelar o compartilhamento dos dados da string), mas é necessário não lançar , por exemplo, C ++ 11 §21.4.5 / 3 “ Lança: Nada.”. E, portanto, ele não pode usar a alocação dinâmica comum para criar um novo buffer para cópia de dados COW. Uma maneira de contornar isso é usar um heap especial onde a memória pode ser reservada sem ser realmente alocada e, em seguida, reservar a quantidade necessária para cada referência lógica a um valor de string. Reservar e cancelar a reserva em tal heap pode ser tempo constante, O (1), e alocar a quantidade que já foi reservada, pode sernoexcept
. A fim de cumprir os requisitos do padrão, com essa abordagem, parece que seria necessário um tal heap especial baseado em reserva por alocador distinto.Notas:
¹ O
const
acessador de item aciona uma cópia de dados COW porque permite que o código do cliente obtenha uma referência ou ponteiro para os dados, que não é permitido invalidar por uma cópia posterior de dados acionada, por exemplo, peloconst
acessador de não item.fonte
std::string
, ao desconsiderar os requisitos O (1), seria ineficiente, é a sua opinião. Não sei o que poderia ser a performance, mas acho que essa afirmação é feita mais pela sensação, pelas vibrações que transmite, do que por qualquer relevância para esta resposta.Eu sempre me perguntei sobre vacas imutáveis: uma vez que a vaca é criada, eu só poderia ser mudado através da designação de outra vaca, portanto, ela estará em conformidade com o padrão.
Tive tempo para experimentá-lo hoje para um teste de comparação simples: um mapa de tamanho N digitado por string / vaca com cada nó segurando um conjunto de todas as strings no mapa (temos o número NxN de objetos).
Com strings de aproximadamente 300 bytes e N = 2000, as vacas são um pouco mais rápidas e usam quase menos memória da ordem de magnitude. Veja abaixo, os tamanhos estão em kbs, a corrida b é com vacas.
fonte