Esta é uma questão de duas partes, tudo sobre a atomicidade de std::shared_ptr
:
1.
Pelo que eu posso dizer, std::shared_ptr
é o único ponteiro inteligente <memory>
que é atômico. Gostaria de saber se há uma versão não atômica do std::shared_ptr
disponível (não consigo ver nada no <memory>
, então também estou aberto a sugestões fora do padrão, como aquelas no Boost). Eu sei que boost::shared_ptr
também é atômico (se BOOST_SP_DISABLE_THREADS
não estiver definido), mas talvez haja outra alternativa? Estou procurando por algo que tenha a mesma semântica std::shared_ptr
, mas sem a atomicidade.
2. Eu entendo porque std::shared_ptr
é atômico; é meio legal. No entanto, não é bom para todas as situações, e C ++ tem historicamente o mantra de "pague apenas pelo que usar". Se eu não estiver usando vários threads, ou se eu estiver usando vários threads, mas não estou compartilhando a propriedade do ponteiro entre os threads, um ponteiro inteligente atômico é um exagero. Minha segunda pergunta é por que uma versão não atômica do std::shared_ptr
C ++ 11 não foi fornecida ? (assumindo que há um porquê ) (se a resposta for simplesmente "uma versão não atômica simplesmente nunca foi considerada" ou "ninguém nunca pediu uma versão não atômica", tudo bem!).
Com a pergunta nº 2, estou me perguntando se alguém já propôs uma versão não atômica de shared_ptr
(para Boost ou o comitê de padrões) (não para substituir a versão atômica de shared_ptr
, mas para coexistir com ela) e foi rejeitada por um Razão específica.
fonte
shared_ptr
teve uma desaceleração significativa devido à sua atomicidade, e a definiçãoBOOST_DISABLE_THREADS
fez uma diferença notável (não sei sestd::shared_ptr
teria o mesmo custo queboost::shared_ptr
teve).shared_ptr
, não usa operações atômicas para o refcount. Consulte (2) em gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html para um patch para o GCC para permitir que a implementação não atômica seja usada mesmo em aplicativos multithread, parashared_ptr
objetos que não são compartilhados entre tópicos. Estou sentado nesse patch há anos, mas estou considerando finalmente enviá-lo para o GCC 4.9Respostas:
Não fornecido pela norma. Pode muito bem haver um fornecido por uma biblioteca de "terceiros". Na verdade, antes do C ++ 11 e antes do Boost, parecia que todos escreviam seu próprio smart pointer contado de referência (incluindo eu).
Esta questão foi discutida na reunião de Rapperswil em 2010. O assunto foi apresentado por um Comentário do Órgão Nacional nº 20 da Suíça. Houve fortes argumentos em ambos os lados do debate, incluindo aqueles que você forneceu em sua pergunta. No entanto, no final da discussão, a votação foi esmagadora (mas não unânime) contra a adição de uma versão não sincronizada (não atômica) de
shared_ptr
.Argumentos contra incluídos:
O código escrito com shared_ptr não sincronizado pode acabar sendo usado em código encadeado no futuro, acabando causando problemas de depuração sem aviso prévio.
Ter um shared_ptr "universal" que é o "caminho único" para trafegar na contagem de referência tem benefícios: Da proposta original :
O custo das atômicas, embora não seja zero, não é avassalador. O custo é mitigado pelo uso de construção de movimento e atribuição de movimento que não precisa usar operações atômicas. Essas operações são comumente usadas para
vector<shared_ptr<T>>
apagar e inserir.Nada proíbe as pessoas de escreverem seu próprio smart pointer não atômico com contagem de referência se isso for realmente o que desejam fazer.
A palavra final do LWG em Rapperswil naquele dia foi:
fonte
Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries.
esse é um raciocínio extremamente estranho. Bibliotecas de terceiros fornecerão seus próprios tipos de qualquer maneira, então por que importaria se eles os fornecessem na forma de std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType>, etc? você sempre terá que adaptar seu código ao que a biblioteca retorna de qualquer maneirastd::shared_ptr<std::string>
algum lugar. Se a biblioteca de outra pessoa também aceita esse tipo, os chamadores podem passar as mesmas strings para nós dois sem a inconveniência ou sobrecarga de conversão entre representações diferentes, e isso é uma pequena vitória para todos.Howard já respondeu bem à pergunta e Nicol fez alguns bons pontos sobre os benefícios de ter um único tipo de ponteiro compartilhado padrão, em vez de muitos tipos incompatíveis.
Embora eu concorde totalmente com a decisão do comitê, acho que há algum benefício em usar um
shared_ptr
tipo do tipo não sincronizado em casos especiais , então investiguei o tópico algumas vezes.Com o GCC, quando o seu programa não usa vários threads, shared_ptr não usa ops atômicos para o refcount. Isso é feito atualizando as contagens de referência por meio de funções de wrapper que detectam se o programa é multithreaded (no GNU / Linux isso é feito simplesmente detectando se o programa está vinculado a
libpthread.so
) e despacha para operações atômicas ou não atômicas de acordo.Percebi há muitos anos que, como o GCC
shared_ptr<T>
é implementado em termos de uma__shared_ptr<T, _LockPolicy>
classe base , é possível usar a classe base com a política de bloqueio de thread único, mesmo em código multithread, usando explicitamente__shared_ptr<T, __gnu_cxx::_S_single>
. Infelizmente, como esse não era um caso de uso pretendido, não funcionava perfeitamente antes do GCC 4.9, e algumas operações ainda usavam as funções de wrapper e, portanto, despachavam para operações atômicas, embora você tenha solicitado explicitamente a_S_single
política. Veja o ponto (2) em http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.htmlpara obter mais detalhes e um patch para o GCC para permitir que a implementação não atômica seja usada mesmo em aplicativos multithread. Fiquei sentado naquele patch por anos, mas finalmente o comprometi para o GCC 4.9, que permite usar um modelo de alias como este para definir um tipo de ponteiro compartilhado que não é seguro para thread, mas é um pouco mais rápido:template<typename T> using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;
Este tipo não seria interoperável com
std::shared_ptr<T>
e somente seria seguro para uso quando fosse garantido que osshared_ptr_unsynchronized
objetos nunca seriam compartilhados entre threads sem sincronização adicional fornecida pelo usuário.É claro que isso é completamente não portável, mas às vezes não tem problema. Com os hacks de pré-processador corretos, seu código ainda funcionaria bem com outras implementações, se
shared_ptr_unsynchronized<T>
fosse um apelido parashared_ptr<T>
, seria um pouco mais rápido com o GCC.Se você estiver usando um GCC anterior ao 4.9, poderá usá-lo adicionando as
_Sp_counted_base<_S_single>
especializações explícitas ao seu próprio código (e garantindo que ninguém instancie__shared_ptr<T, _S_single>
sem incluir as especializações, para evitar violações de ODR.) Adicionar essas especializações destd
tipos é tecnicamente indefinido, mas seria funcionam na prática, porque neste caso não há diferença entre eu adicionar as especializações ao GCC ou você adicioná-las ao seu próprio código.fonte
std::shared_ptr
,std::__shared_ptr
,__default_lock_policy
e tal. Essa resposta confirmou o que entendi do código.Alguém poderia facilmente perguntar por que não existe um ponteiro intrusivo ou qualquer outra variação possível de ponteiros compartilhados que alguém possa ter.
O projeto
shared_ptr
do Boost foi o de criar uma língua-franca padrão mínimo de indicadores inteligentes. Que, de modo geral, você pode simplesmente puxar da parede e usá-lo. É algo que pode ser usado em uma ampla variedade de aplicativos. Você pode colocá-lo em uma interface e, provavelmente, boas pessoas estarão dispostas a usá-lo.O encadeamento só vai se tornar mais prevalente no futuro. Na verdade, com o passar do tempo, o encadeamento geralmente será um dos principais meios de obter desempenho. Exigir que o ponteiro inteligente básico faça o mínimo necessário para suportar o encadeamento facilita essa realidade.
Colocar no padrão meia dúzia de ponteiros inteligentes com pequenas variações entre eles, ou ainda pior, um ponteiro inteligente baseado em políticas, teria sido terrível. Todos escolheriam o ponteiro que mais gostassem e rejeitariam todos os outros. Ninguém seria capaz de se comunicar com mais ninguém. Seria como as situações atuais com strings C ++, onde cada um tem seu próprio tipo. Só que pior, porque a interoperação com strings é muito mais fácil do que a interoperação entre classes de ponteiros inteligentes.
Boost e, por extensão, o comitê, escolheram um ponteiro inteligente específico para usar. Fornecia um bom equilíbrio de recursos e era amplamente e comumente usado na prática.
std::vector
tem algumas ineficiências em comparação com arrays nus em alguns casos extremos. Tem algumas limitações; alguns usuários realmente querem ter um limite rígido para o tamanho de avector
, sem usar um alocador de lançamento. No entanto, o comitê não planejouvector
ser tudo para todos. Ele foi projetado para ser um bom padrão para a maioria dos aplicativos. Aqueles para quem não pode funcionar podem simplesmente escrever uma alternativa que atenda às suas necessidades.Da mesma forma que você pode para um indicador inteligente se
shared_ptr
a atomicidade de é um fardo. Então, novamente, você também pode considerar não copiá-los tanto.fonte
Estou preparando uma palestra sobre shared_ptr no trabalho. Tenho usado um boost modificado shared_ptr com evitar malloc separado (como o que make_shared pode fazer) e um parâmetro de modelo para política de bloqueio como shared_ptr_unsynchronized mencionado acima. Estou usando o programa de
http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html
como um teste, depois de limpar as cópias compartilhadas_ptr desnecessárias. O programa usa apenas o thread principal e o argumento de teste é mostrado. O env de teste é um notebook executando o Linuxmint 14. Aqui está o tempo gasto em segundos:
Apenas a versão 'std' usa -std = cxx11, e o -pthread provavelmente muda a lock_policy na classe g ++ __shared_ptr.
A partir desses números, vejo o impacto das instruções atômicas na otimização do código. O caso de teste não usa nenhum contêiner C ++, mas
vector<shared_ptr<some_small_POD>>
provavelmente sofrerá se o objeto não precisar da proteção de thread. Boost sofre menos provavelmente porque o malloc adicional está limitando a quantidade de inlining e otimização de código.Ainda estou para encontrar uma máquina com núcleos suficientes para testar a escalabilidade das instruções atômicas, mas usar std :: shared_ptr apenas quando necessário é provavelmente melhor.
fonte
Boost fornece um
shared_ptr
que não é atômico. É chamadolocal_shared_ptr
e pode ser encontrado na biblioteca de smart pointers do boost.fonte
shared_ptr
com um contador de qualquer maneira, embora seja local? Ou você quer dizer que há outro problema com isso? Os médicos dizem que a única diferença é que isso não é atômico.local_shared_ptr
eshared_ptr
são idênticos, exceto para atômicos. Estou genuinamente interessado em descobrir se o que você está dizendo é verdade porque eu usolocal_shared_ptr
em aplicativos que exigem alto desempenho.