std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
Muitas postagens do google e do stackoverflow existem sobre isso, mas não consigo entender por que make_shared
é mais eficiente do que usar diretamente shared_ptr
.
Alguém pode me explicar passo a passo a sequência de objetos criados e operações realizadas por ambos, para que eu possa entender como make_shared
é eficiente. Eu dei um exemplo acima para referência.
c++
c++11
shared-ptr
Anup Buchke
fonte
fonte
make_shared
você pode escreverauto p1(std::make_shared<A>())
e p1 terá o tipo correto.Respostas:
A diferença é que
std::make_shared
executa uma alocação de heap, enquanto que chamar ostd::shared_ptr
construtor executa duas.Onde as alocações de heap acontecem?
std::shared_ptr
gerencia duas entidades:std::make_shared
executa uma única contabilidade de alocação de heap para o espaço necessário para o bloco de controle e os dados. No outro caso,new Obj("foo")
chama uma alocação de pilha para os dados gerenciados e ostd::shared_ptr
construtor executa outra para o bloco de controle.Para mais informações, consulte as notas de implementação em cppreference .
Atualização I: Exceção-Segurança
NOTA (30/08/2019) : este não é um problema desde o C ++ 17, devido às alterações na ordem de avaliação dos argumentos da função. Especificamente, é necessário que cada argumento de uma função seja executado completamente antes da avaliação de outros argumentos.
Como o OP parece estar se perguntando sobre o lado da exceção-segurança, atualizei minha resposta.
Considere este exemplo,
Como o C ++ permite a ordem arbitrária de avaliação de subexpressões, uma ordem possível é:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Agora, suponha que recebemos uma exceção lançada na etapa 2 (por exemplo, exceção de falta de memória, o
Rhs
construtor lançou alguma exceção). Em seguida, perdemos a memória alocada na etapa 1, pois nada terá a chance de limpá-la. O principal do problema aqui é que o ponteiro bruto não foi passado para ostd::shared_ptr
construtor imediatamente.Uma maneira de corrigir isso é fazê-lo em linhas separadas, para que essa ordenação arbitrária não possa ocorrer.
A maneira preferida de resolver isso, é claro, é usar
std::make_shared
.Atualização II: Desvantagem de
std::make_shared
Citando os comentários de Casey :
Por que instâncias de
weak_ptr
s mantêm o bloco de controle ativo?Deve haver uma maneira de
weak_ptr
s determinar se o objeto gerenciado ainda é válido (por exemplo, paralock
). Eles fazem isso verificando o número deshared_ptr
s que possuem o objeto gerenciado, que é armazenado no bloco de controle. O resultado é que os blocos de controle permanecem ativos até ashared_ptr
contagem e aweak_ptr
contagem atingirem 0.De volta a
std::make_shared
Como
std::make_shared
faz uma única alocação de heap para o bloco de controle e o objeto gerenciado, não há como liberar a memória para o bloco de controle e o objeto gerenciado independentemente. Devemos esperar até que possamos liberar o bloco de controle e o objeto gerenciado, o que acontece até que não haja nenhumshared_ptr
ou maisweak_ptr
ativos.Suponha que, em vez disso, realizássemos duas alocações de heap para o bloco de controle e o objeto gerenciado via
new
eshared_ptr
construtor. Em seguida, liberamos a memória para o objeto gerenciado (talvez mais cedo) quando não há nenhumshared_ptr
ativo, e liberamos a memória para o bloco de controle (talvez mais tarde) quando não há nenhumweak_ptr
ativo.fonte
make_shared
: como existe apenas uma alocação, a memória do apontador não pode ser desalocada até que o bloco de controle não esteja mais em uso. Aweak_ptr
pode manter o bloco de controle ativo indefinidamente.make_shared
emake_unique
de forma consistente, você não vai ter possuir ponteiros crus um pode tratar todas as ocorrências denew
como um cheiro de código.shared_ptr
, e nãoweak_ptr
s, chamarreset()
ashared_ptr
instância excluirá o bloco de controle. Mas isso é independente ou semake_shared
foi usado. O usomake_shared
faz a diferença, pois pode prolongar a vida útil da memória alocada para o objeto gerenciado . Quando ashared_ptr
contagem atinge 0, o destruidor do objeto gerenciado é chamado independentementemake_shared
, mas liberar sua memória só pode ser feito se não tivermake_shared
sido usado. Espero que isso torne mais claro.O ponteiro compartilhado gerencia o próprio objeto e um pequeno objeto que contém a contagem de referência e outros dados de manutenção.
make_shared
pode alocar um único bloco de memória para armazenar os dois; a construção de um ponteiro compartilhado de um ponteiro para um objeto já alocado precisará alocar um segundo bloco para armazenar a contagem de referência.Além dessa eficiência, usar
make_shared
significa que você não precisa lidar comnew
ponteiros brutos, fornecendo melhor segurança para exceções - não há possibilidade de lançar uma exceção após alocar o objeto, mas antes de atribuí-lo ao ponteiro inteligente.fonte
Há outro caso em que as duas possibilidades diferem, além das já mencionadas: se você precisar chamar um construtor não público (protegido ou privado), o make_shared poderá não ser capaz de acessá-lo, enquanto a variante do novo funciona bem .
fonte
new
, caso contrário eu teria usadomake_shared
. Aqui está uma pergunta relacionada sobre isso: stackoverflow.com/questions/8147027/… .Se você precisar de um alinhamento especial da memória no objeto controlado por shared_ptr, não poderá confiar no make_shared, mas acho que é a única boa razão para não usá-lo.
fonte
Eu vejo um problema com std :: make_shared, ele não suporta construtores privados / protegidos
fonte
Shared_ptr
: Executa duas alocações de heapMake_shared
: Executa apenas uma alocação de heapfonte
Sobre eficiência e preocupação com o tempo gasto na alocação, fiz este teste simples abaixo, criei várias instâncias por essas duas maneiras (uma de cada vez):
O problema é que o uso do make_shared levou o dobro do tempo comparado ao uso do novo. Portanto, usando new, existem duas alocações de heap em vez de uma usando make_shared. Talvez este seja um teste estúpido, mas não mostra que o uso do make_shared leva mais tempo do que o novo? Claro, estou falando apenas do tempo usado.
fonte
Penso que a parte de exceção da segurança da resposta do sr. Mpark ainda é uma preocupação válida. ao criar um shared_ptr como este: shared_ptr <T> (novo T), o novo T pode ter êxito, enquanto a alocação do bloco de controle do shared_ptr pode falhar. Nesse cenário, o T alocado recentemente vazará, pois o shared_ptr não tem como saber que foi criado no local e é seguro excluí-lo. Ou eu estou esquecendo de alguma coisa? Eu não acho que as regras mais rígidas sobre avaliação de parâmetros de função ajudem de alguma forma aqui ...
fonte