Por que usar std :: make_unique em C ++ 17?

96

Pelo que entendi, C ++ 14 foi introduzido std::make_uniqueporque, como resultado da ordem de avaliação dos parâmetros não ser especificada, isso não era seguro:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(Explicação: se a avaliação primeiro alocar a memória para o ponteiro bruto, em seguida, chamar g()e uma exceção for lançada antes da std::unique_ptrconstrução, a memória será perdida.)

Chamar std::make_uniqueera uma forma de restringir a ordem da chamada, tornando as coisas seguras:

f(std::make_unique<MyClass>(param), g());             // Syntax B

Desde então, C ++ 17 esclareceu a ordem de avaliação, tornando Sintaxe Um cofre também, então aqui está a minha pergunta: existe ainda uma razão para usar std::make_uniqueao longo std::unique_ptrdo construtor em C ++ 17? Voce pode dar alguns exemplos?

A partir de agora, a única razão que posso imaginar é que ele permite digitar MyClassapenas uma vez (supondo que você não precise confiar no polimorfismo com std::unique_ptr<Base>(new Derived(param))). No entanto, esse parece um motivo muito fraco, especialmente quando std::make_uniquenão permite especificar um deleter enquanto std::unique_ptro construtor de faz.

E só para ficar claro, não estou defendendo a remoção std::make_uniqueda Biblioteca Padrão (mantê-la faz sentido, pelo menos para compatibilidade com versões anteriores), mas sim me perguntando se ainda há situações em que é fortemente preferidostd::unique_ptr

Eterno
fonte
4
No entanto, essa parece uma razão muito fraca -> Por que é uma razão fraca? Reduz efetivamente a duplicação de código do tipo. Quanto ao eliminador, com que frequência você usa um eliminador personalizado quando usa std::unique_ptr? Não é um argumento contramake_unique
llllllllll
2
Digo que é um motivo fraco porque se não houvesse std::make_uniqueem primeiro lugar, não acho que seria motivo suficiente para adicioná-lo ao STL, especialmente quando é uma sintaxe menos expressiva do que usar o construtor, não mais
Eterno
1
Se você tiver um programa, criado em c ++ 14, usando make_unique, não deseja que a função seja removida de stl. Ou se você quiser que seja compatível com versões anteriores.
Serge
2
@Serge Esse é um bom ponto, mas está um pouco além do objeto da minha pergunta. Vou fazer uma edição para deixar mais claro
Eterno
1
@Eternal, por favor, pare de se referir à Biblioteca C ++ Standard como STL, pois é incorreta e cria confusão. Consulte stackoverflow.com/questions/5205491/…
Marandil

Respostas:

73

Você está certo de que o principal motivo foi removido. Há ainda os motivos de não usar novas diretrizes e que são menos digitadores (não precisa repetir o tipo ou usar a palavra new). Admito que esses não são argumentos fortes, mas eu realmente gosto de não ver newno meu código.

Também não se esqueça da consistência. Você absolutamente deveria estar usando, make_sharedentão o uso make_uniqueé natural e se encaixa no padrão. Então, é trivial mudar std::make_unique<MyClass>(param)para std::make_shared<MyClass>(param)(ou o contrário) onde a sintaxe A requer muito mais reescrita.

NathanOliver
fonte
40
@reggaeguitar Se eu vir um new, preciso parar e pensar: quanto tempo esse ponteiro vai durar? Manusei corretamente? Se houver uma exceção, tudo está limpo corretamente? Gostaria de não me fazer essas perguntas e perder meu tempo com isso e, se não usar new, não preciso fazer essas perguntas.
NathanOliver
5
Imagine que você faça um grep em todos os arquivos fonte do seu projeto e não encontre nenhum new. Isso não seria maravilhoso?
Sebastian Mach
5
A principal vantagem da diretriz "não use novos" é que é simples, portanto, é uma diretriz fácil de fornecer aos desenvolvedores menos experientes com os quais você possa estar trabalhando. Eu não tinha percebido a princípio, mas isso tem valor por si só
Eterno
@NathanOliver Você realmente não tem que usar absolutamentestd::make_shared - imagine um caso onde o objeto alocado é grande e há muitos std::weak_ptr-s apontando para ele: seria melhor deixar o objeto ser deletado assim que o último compartilhado O ponteiro é destruído e fica com apenas uma pequena área compartilhada.
Dev Null
1
@NathanOliver você não faria. O que estou falando é a desvantagem de std::make_shared stackoverflow.com/a/20895705/8414561 onde a memória que foi usada para armazenar o objeto não pode ser liberada até que o último std::weak_ptrtenha ido (mesmo se todos os std::shared_ptr-s apontando para ele (e conseqüentemente, o próprio objeto) já foi destruído).
Dev Null
50

make_uniquedistingue Tde T[]e T[N], unique_ptr(new ...)não.

Você pode obter facilmente um comportamento indefinido passando um ponteiro que foi new[]ed para a unique_ptr<T>, ou passando um ponteiro que foi newed para a unique_ptr<T[]>.

Caleth
fonte
É pior: não apenas não, como também é totalmente incapaz de fazê-lo.
Deduplicator de
21

O motivo é ter um código mais curto sem duplicatas. Comparar

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

Você salva MyClass, newe chaves. Custa apenas um caráter mais na composição , em comparação com o PTR .

SM
fonte
2
Bem, como eu disse na pergunta, posso ver que é menos digitar com apenas uma menção de MyClass, mas eu queria saber se há um motivo mais forte para usá-lo
Eterno
2
Em muitos casos, o guia de dedução ajudaria a eliminar a <MyClass>parte da primeira variante.
AnT de
9
Já foi dito nos comentários para outras respostas, mas enquanto c ++ 17 introduziu dedução de tipo de template para construtores, no caso de std::unique_ptrnão ser permitido. Tem a ver com distinguir std::unique_ptr<T>estd::unique_ptr<T[]>
Eterno
19

Cada uso de newtem que ser cuidadosamente auditado para verificar a exatidão ao longo da vida; ele é excluído? Apenas uma vez?

Cada uso de make_uniquenão faz essas características extras; contanto que o objeto proprietário tenha vida útil "correta", ele recursivamente faz com que o ponteiro exclusivo tenha "correto".

Agora, é verdade que unique_ptr<Foo>(new Foo())é idêntico em todas as maneiras 1 a make_unique<Foo>(); requer apenas um simples "grep seu código-fonte para todos os usos de newpara auditá-los".


1 , na verdade, um, em geral o caso. O encaminhamento perfeito não é perfeito, {}init padrão, arrays são exceções.

Yakk - Adam Nevraumont
fonte
Tecnicamente, unique_ptr<Foo>(new Foo)não é exatamente idêntico a make_unique<Foo>()... o último new Foo()sim. Mas, caso contrário, sim.
Barry de
@barry true, novo operador sobrecarregado é possível.
Yakk - Adam Nevraumont
@dedup que feitiçaria horrível em C ++ 17 é essa?
Yakk - Adam Nevraumont
2
@Deduplicator, enquanto o c ++ 17 introduziu a dedução do tipo de modelo para construtores, no caso de std::unique_ptrnão ser permitido. Tem a ver com distinguir std::unique_ptr<T>estd::unique_ptr<T[]>
Eterno
@ Yakk-AdamNevraumont Eu não quis dizer sobrecarregar new, apenas quis dizer default-init vs value-init.
Barry
0

Desde então, C ++ 17 esclareceu a ordem de avaliação, tornando a Sintaxe A segura também

Isso realmente não é bom o suficiente. Contar com uma cláusula técnica recentemente introduzida como garantia de segurança não é uma prática muito robusta:

  • Alguém pode compilar este código em C ++ 14.
  • Você estaria encorajando o uso de raw new's em outro lugar, por exemplo, copiando e colando seu exemplo.
  • Como o SM sugere, uma vez que há duplicação de código, um tipo pode ser alterado sem que o outro seja alterado.
  • Algum tipo de refatoração automática de IDE pode mover isso para newoutro lugar (ok, certo, não há muita chance disso).

Geralmente, é uma boa ideia que seu código seja apropriado / robusto / claramente válido sem recorrer a camadas de linguagem, procurando por cláusulas técnicas menores ou obscuras no padrão.

(este é essencialmente o mesmo argumento que apresentei aqui sobre a ordem de destruição da tupla.)

einpoklum
fonte
-1

Considere a função void (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}

Suponha que new A () seja bem-sucedido, mas new B () lance uma exceção: você o captura para retomar a execução normal do seu programa. Infelizmente, o padrão C ++ não exige que o objeto A seja destruído e sua memória desalocada: a memória vaza silenciosamente e não há como limpá-la. Ao envolver A e B em std :: make_uniques, você tem certeza de que o vazamento não ocorrerá:

void function (std :: make_unique (), std :: make_unique ()) {...} O ponto aqui é que std :: make_unique e std :: make_unique são agora objetos temporários, e a limpeza de objetos temporários é especificada corretamente em o padrão C ++: seus destruidores serão acionados e a memória liberada. Então, se você puder, sempre prefira alocar objetos usando std :: make_unique e std :: make_shared.

Dhanya Gopinath
fonte
4
O autor especificou explicitamente em questão que em C ++ 17 não devem ocorrer mais vazamentos. "Desde então, C ++ 17 esclareceu a ordem de avaliação, tornando a Sintaxe A segura também, então fica a minha pergunta: (...)". Você não respondeu à pergunta dele.
R2RT