A diferença é que std::make_shared
executa uma alocação de heap, enquanto que chamar o std::shared_ptr
construtor executa duas.
Onde as alocações de heap acontecem?
std::shared_ptr
gerencia duas entidades:
- o bloco de controle (armazena metadados como contagens de ref, deletador apagado por tipo etc.)
- o objeto que está sendo gerenciado
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 o std::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,
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
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 o std::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.
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
A maneira preferida de resolver isso, é claro, é usar std::make_shared
.
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Atualização II: Desvantagem de std::make_shared
Citando os comentários de Casey :
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. A weak_ptr
pode manter o bloco de controle ativo indefinidamente.
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, para lock
). Eles fazem isso verificando o número de shared_ptr
s que possuem o objeto gerenciado, que é armazenado no bloco de controle. O resultado é que os blocos de controle permanecem ativos até a shared_ptr
contagem e a weak_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 nenhum shared_ptr
ou mais weak_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
e shared_ptr
construtor. Em seguida, liberamos a memória para o objeto gerenciado (talvez mais cedo) quando não há nenhum shared_ptr
ativo, e liberamos a memória para o bloco de controle (talvez mais tarde) quando não há nenhum weak_ptr
ativo.