Pergunto-me que méritos possíveis o copy-on-write tem? Naturalmente, não espero opiniões pessoais, mas cenários práticos do mundo real em que pode ser técnica e praticamente benéfica de maneira tangível. Por tangível, quero dizer algo mais do que poupar a digitação de um &
personagem.
Para esclarecer, essa pergunta está no contexto de tipos de dados, em que a construção de atribuição ou cópia cria uma cópia superficial implícita, mas as modificações nela criam uma cópia profunda implícita e aplicam as alterações a ela, em vez do objeto original.
A razão pela qual estou perguntando é que não consigo encontrar nenhum mérito de ter a COW como um comportamento implícito padrão. Eu uso o Qt, que implementou o COW para muitos tipos de dados, praticamente todos os quais possuem algum armazenamento alocado dinamicamente subjacente. Mas como isso realmente beneficia o usuário?
Um exemplo:
QString s("some text");
QString s1 = s; // now both s and s1 internally use the same resource
qDebug() << s1; // const operation, nothing changes
s1[o] = z; // s1 "detaches" from s, allocates new storage and modifies first character
// s is still "some text"
O que ganhamos usando o COW neste exemplo?
Se tudo o que pretendemos fazer é usar operações const, s1
é redundante e pode ser usado s
.
Se pretendemos alterar o valor, o COW apenas atrasa a cópia do recurso até a primeira operação não const, ao custo (ainda que mínimo) de incrementar a contagem de ref para o compartilhamento implícito e a desanexação do armazenamento compartilhado. Parece que toda a sobrecarga envolvida na COW é inútil.
Não é muito diferente no contexto da passagem de parâmetros - se você não pretende modificar o valor, passe como referência const; se quiser modificar, faça uma cópia implícita e profunda se não quiser modificar o objeto original ou passe por referência, se desejar modificá-lo. Novamente, o COW parece uma sobrecarga desnecessária que não alcança nada e apenas adiciona uma limitação de que você não pode modificar o valor original, mesmo que queira, pois qualquer alteração será destacada do objeto original.
Portanto, dependendo se você conhece a COW ou não o conhece, pode resultar em código com intenção obscura e sobrecarga desnecessária, ou comportamento completamente confuso que não corresponde às expectativas e deixa você coçando a cabeça.
Para mim, parece que existem soluções mais eficientes e legíveis, se você deseja evitar uma cópia profunda desnecessária ou se pretende fazer uma. Então, onde está o benefício prático da vaca? Suponho que deve haver algum benefício, pois é usado em uma estrutura tão popular e poderosa.
Além disso, pelo que li, o COW agora é explicitamente proibido na biblioteca padrão do C ++. Não sei se os truques que vejo nele têm algo a ver com isso, mas, de qualquer forma, deve haver uma razão para isso.
[]
operador. Portanto, o COW permite um design ruim - que não parece muito benéfico :) O ponto do último parágrafo parece válido, mas eu próprio não sou um grande fã de comportamento implícito - as pessoas tendem a considerá-lo um dado adquirido e depois têm é difícil descobrir por que o código não funciona como o esperado e fica pensando até descobrirem o que está oculto por trás do comportamento implícito.const_cast
parece que ele pode quebrar o COW com a mesma facilidade que ele pode quebrar a passagem por referência const. Por exemplo,QString::constData()
retorna umconst QChar *
-const_cast
que e a COW entra em colapso - você modifica os dados do objeto original.char*
obviamente não está ciente). Quanto ao comportamento implícito, acho que você está certo, há problemas com ele. O design da API é um equilíbrio constante entre os dois extremos. Implícito demais, e as pessoas começam a confiar em comportamentos especiais como se isso fosse parte de fato das especificações. Muito explícito, e a API se torna muito pesada à medida que você expõe muitos detalhes subjacentes que não eram realmente importantes e, de repente, são gravados nas especificações da API.string
classes tenham comportamento COW porque os designers do compilador perceberam que um grande corpo de código estava copiando seqüências de caracteres em vez de usar const-reference. Se eles adicionassem o COW, eles poderiam otimizar esse caso e fazer mais pessoas felizes (e isso era legal até C ++ 11). Aprecio a posição deles: embora eu sempre passei minhas cordas por referência const, vi todo esse lixo sintático que apenas prejudica a legibilidade. Eu odeio escreverconst std::shared_ptr<const std::string>&
apenas para capturar a semântica correta!Para seqüências de caracteres e coisas do tipo, isso parece pessimizar casos de uso mais comuns do que não, pois o caso comum de seqüências de caracteres geralmente é de seqüências pequenas, e a sobrecarga do COW tende a compensar muito o custo de simplesmente copiar a sequência de caracteres pequena. Uma pequena otimização de buffer faz muito mais sentido para mim, para evitar a alocação de heap nesses casos, em vez das cópias de string.
Porém, se você tem um objeto mais pesado, como um andróide, e deseja copiá-lo e apenas substituir seu braço cibernético, a COW parece bastante razoável como uma maneira de manter uma sintaxe mutável, evitando a necessidade de copiar profundamente todo o andróide apenas para dê à cópia um braço único. Torná-lo imutável como uma estrutura de dados persistente nesse ponto pode ser superior, mas uma "COW parcial" aplicada em partes individuais do android parece razoável para esses casos.
Nesse caso, as duas cópias do androide compartilhariam / instariam o mesmo tronco, pernas, pés, cabeça, pescoço, ombros, pélvis etc. Os únicos dados que seriam diferentes entre eles e não compartilhados são o braço que foi feito exclusivo para o segundo androide sobrescrevendo seu braço.
fonte
std::vector<std::string>
antes de termosemplace_back
e mover a semântica em C ++ 11) . Mas também estamos basicamente usando instanciamento. O sistema de nós pode ou não modificar os dados. Temos coisas como nós de passagem que não fazem nada com a entrada, mas apenas produzem uma cópia (eles estão lá para a organização do usuário de seu programa). Nesses casos, todos os dados é superficial copiado para tipos complexos ...A
é copiado e nada é feito para o objetoB
, é uma cópia superficial barata para tipos de dados complexos, como malhas. Agora, se modificarmosB
, os dados em que modificamosB
se tornam únicos por meio do COW, masA
são intocados (exceto por algumas contagens de referência atômica).