make_unique e encaminhamento perfeito

216

Por que não há std::make_uniquemodelo de função na biblioteca C ++ 11 padrão? eu acho

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

um pouco detalhado. O seguinte não seria muito melhor?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

Isso oculta newbem e apenas menciona o tipo uma vez.

De qualquer forma, aqui está minha tentativa de implementação de make_unique:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Levei um bom tempo para std::forwardcompilar o material, mas não tenho certeza se está correto. É isso? O que exatamente std::forward<Args>(args)...significa? O que o compilador faz disso?

fredoverflow
fonte
1
Certamente já tivemos essa discussão antes ... observe também que é unique_ptrnecessário um segundo parâmetro de modelo que você deve permitir de alguma forma - que é diferente shared_ptr.
Kerrek SB 12/08/11
5
@Kerrek: Eu não acho que faria sentido para parametrizar make_uniquecom um deleter costume, porque, obviamente, ele aloca via velha e simples newe, portanto, deve usar planície antiga delete:)
fredoverflow
1
@ Fred: Isso é verdade. Então, a proposta make_uniqueseria limitada à newalocação ... bem, tudo bem se você quiser escrever, mas posso ver por que algo assim não faz parte do padrão.
12111 Kerrek SB
4
Na verdade, eu gosto de usar um make_uniquemodelo, pois o construtor de std::unique_ptré explícito e, portanto, é detalhado retornar unique_ptrde uma função. Além disso, eu prefiro usar do auto p = make_unique<foo>(bar, baz)que std::unique_ptr<foo> p(new foo(bar, baz)).
Alexandre C.
5
make_uniqueestá chegando C++14, consulte isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting
Stefan

Respostas:

157

Herb Sutter, presidente do comitê de padronização C ++, escreve em seu blog :

O fato de o C ++ 11 não incluir make_uniqueé parcialmente um descuido, e certamente será adicionado no futuro.

Ele também fornece uma implementação idêntica à fornecida pelo OP.

Edit: std::make_unique agora faz parte do C ++ 14 .

Johan Råde
fonte
2
Um make_uniquemodelo de função não garante, por si só, chamadas seguras de exceção. Ele depende da convenção de que o chamador a usa. Por outro lado, a verificação estrita de tipo estático (que é o principal diferencial entre C ++ e C) baseia-se na ideia de impor segurança, por meio de tipos. E, para isso, make_uniquepode ser simplesmente uma classe em vez de uma função. Por exemplo, consulte o artigo do meu blog em maio de 2010. Também está vinculado à discussão no blog da Herb.
Saúde e hth. - Alf
1
@ DavidRodríguez-dribeas: leia o blog de Herb para entender o problema de segurança das exceções. esqueça "não projetado para ser derivado", isso é apenas lixo. em vez da minha sugestão de criar make_uniqueuma classe, agora acho que é melhor transformá-la em uma função que produza make_unique_t, a razão disso é um problema com a análise mais perversa :-).
Saúde e hth. - Alf
1
@ Cheersandhth.-Alf: Talvez tenhamos lido um artigo diferente, porque o que acabei de ler afirma claramente que make_uniqueoferece uma forte garantia de exceção. Ou talvez você esteja misturando a ferramenta ao seu uso, caso em que nenhuma função é segura para exceções. Considere void f( int *, int* ){}, oferece claramente a no throwgarantia, mas, por sua linha de raciocínio, não é uma exceção segura, pois pode ser mal utilizada. Pior, void f( int, int ) {}também não é exceção segura !: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez - dribeas
2
@ Cheersandhth.-Alf: Eu perguntei sobre as questões de exceção em make_unique implementado como acima e você me apontar para um artigo de Sutter, o artigo que meu google-fu me apontou para estados que make_uniqueoferece a garantia forte exceção , o que contradiz a sua declaração acima. Se você tem um artigo diferente Eu estou interessado em lê-lo. Portanto, minha pergunta original é: Como make_unique(como definido acima) não é exceção segura? (Sidenote: sim, eu acho que make_unique melhora a segurança de exceção em lugares onde ele pode ser aplicado)
David Rodríguez - dribeas
3
... também não estou buscando uma função menos segura de usar make_unique. Primeiro, não vejo como este é inseguro e não vejo como adicionar um tipo extra o tornaria mais seguro . O que sei é que estou interessado em entender os problemas que esta implementação pode ter - não consigo ver nenhum - e como uma implementação alternativa os resolveria. Quais são as convenções que make_uniquedependem? Como você usaria a verificação de tipo para reforçar a segurança? Essas são as duas perguntas para as quais eu adoraria uma resposta.
David Rodríguez - dribeas
78

Bom, mas Stephan T. Lavavej (mais conhecido como STL) tem uma solução melhor para make_unique, que funciona corretamente para a versão do array.

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

Isso pode ser visto em seu vídeo Core C ++ 6 .

Uma versão atualizada da versão do make_unique da STL está agora disponível como N3656 . Esta versão foi adotada no rascunho C ++ 14.

tominator
fonte
make_unique foi proposto por Stephen T Lavalej para a próxima atualização padrão.
tominator
Aqui está um gosto dele falando sobre adicioná-lo. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
tominator
por que fazer todas as edições desnecessárias xeo, estava tudo bem como estava. Esse código era exatamente como Stephen T. Lavalej colocou, e ele trabalha para o dinkumware, que mantém a biblioteca std. Você já comentou sobre esse muro, então deve saber.
tominator
3
A implementação para make_uniquedeve ir em um cabeçalho. Os cabeçalhos não devem importar um espaço para nome (consulte o Item # 59 no livro "Padrões de codificação C ++" de Sutter / Alexandrescu). As mudanças do Xeo ajudam a evitar o incentivo a práticas ruins.
Bret Kuhn
Infelizmente, não é suportado pelo VC2010, mesmo VC2012 eu acho, que tanto não suporta parâmetro tempalte varidic
zhaorufei
19

std::make_sharednão é apenas uma abreviação para std::shared_ptr<Type> ptr(new Type(...));. Faz algo que você não pode fazer sem ele.

Para fazer seu trabalho, é std::shared_ptrnecessário alocar um bloco de rastreamento, além de reter o armazenamento para o ponteiro real. No entanto, como std::make_sharedaloca o objeto real, é possível que std::make_sharedaloque o objeto e o bloco de rastreamento no mesmo bloco de memória.

Portanto, embora std::shared_ptr<Type> ptr = new Type(...);houvesse duas alocações de memória (uma para a new, uma no std::shared_ptrbloco de rastreamento), std::make_shared<Type>(...)alocaria um bloco de memória.

Isso é importante para muitos usuários em potencial de std::shared_ptr. A única coisa que um std::make_uniquefaria seria ser um pouco mais conveniente. Nada além disso.

Nicol Bolas
fonte
2
Não é necessário. Sugerido, mas não obrigatório.
Filhote de cachorro
3
Não é apenas por conveniência, também melhoraria a segurança de exceções em certos casos. Veja a resposta do Kerrek SB para isso.
Piotr99
19

Embora nada impeça você de escrever seu próprio ajudante, acredito que o principal motivo para fornecer make_shared<T>na biblioteca é que ele realmente cria um tipo interno de ponteiro compartilhado diferente do shared_ptr<T>(new T)que é alocado de maneira diferente, e não há como conseguir isso sem o dedicado ajudante.

Seu make_uniqueinvólucro, por outro lado, é mero açúcar sintático em torno de uma newexpressão; portanto, embora pareça agradável aos olhos, não traz nada newpara a mesa. Correção: isso não é verdade: ter uma chamada de função para quebrar a newexpressão fornece segurança de exceção, por exemplo, no caso em que você chama uma função void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Ter dois news brutos que não são sequenciais um em relação ao outro significa que, se uma nova expressão falhar com uma exceção, a outra poderá vazar recursos. Quanto ao porquê não existe make_uniqueno padrão: foi apenas esquecido. (Isso acontece ocasionalmente. Também não há global std::cbeginno padrão, mesmo que deva haver um.)

Observe também que unique_ptrutiliza um segundo parâmetro de modelo que você deve permitir de alguma forma; é diferente de shared_ptr, que usa apagamento de tipo para armazenar deleters personalizados sem torná-los parte do tipo.

Kerrek SB
fonte
1
@FredOverflow: O ponteiro compartilhado é uma classe relativamente complicada; internamente, ele mantém um bloco de controle de referência polimórfico, mas existem vários tipos diferentes de blocos de controle. shared_ptr<T>(new T)usa um deles, make_shared<T>()usa um diferente. Permitir que isso seja uma coisa boa, e a versão make-shared é, em certo sentido, o ponteiro compartilhado mais leve que você pode obter.
Kerrek SB
15
@FredOverflow: shared_ptraloca um bloco de memória dinâmica para manter a contagem e a ação "eliminador" quando você cria um shared_ptr. Se você passar o ponteiro explicitamente, ele precisará criar um bloco "novo"; se você usá- make_sharedlo, poderá agrupar seu objeto e os dados do satélite em um único bloco de memória (um new) resultando em alocação / desalocação mais rápida, menos fragmentação e (normalmente ) melhor comportamento de cache.
Matthieu M.
2
Eu acho que eu preciso re-relógio shared_ptr e amigos ...
fredoverflow
4
-1 "Seu invólucro make_unique, por outro lado, é mero açúcar sintático em torno de uma nova expressão; portanto, embora pareça agradável aos olhos, não traz nada de novo à mesa." está errado. Traz a possibilidade de invocação de função segura de exceção. Não traz uma garantia, no entanto; para isso, precisaria ser de classe para que os argumentos formais pudessem ser declarados para essa classe (Herb descreveu isso como a diferença entre optar por entrar e sair).
Saúde e hth. - Alf
1
@ Cheersandhth.-Alf: Sim, é verdade. Eu já percebi isso. Vou editar a resposta.
Kerrek SB
13

No C ++ 11 ...é usado (no código do modelo) também para "expansão de pacote".

O requisito é que você o utilize como sufixo de uma expressão que contenha um pacote de parâmetros não expandido, e ele simplesmente aplicará a expressão a cada um dos elementos do pacote.

Por exemplo, desenvolvendo seu exemplo:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

O último está incorreto, eu acho.

Além disso, o pacote de argumentos não pode ser passado para uma função não expandida. Não tenho certeza sobre um pacote de parâmetros de modelo.

Matthieu M.
fonte
1
É bom ver isso explicado. Por que não incluir também o parâmetro do modelo variável?
Kerrek SB
@ Kerrek: porque não tenho tanta certeza sobre sua sintaxe estranha e não sei se muitas pessoas brincaram. Então, continuarei com o que sei. Pode justificar uma entrada de FAQ do c ++, se alguém tiver sido motivado e conhecedor o suficiente, pois a sintaxe variada é bastante completa.
Matthieu M.
A sintaxe é std::forward<Args>(args)..., que se expande para forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB
1
: Eu acho que forwardrealmente sempre exige um parâmetro de modelo, não é?
Kerrek SB
1
@ Howard: certo! Forçando instanciação aciona o aviso ( ideone.com/GDNHb )
Matthieu M.
5

Inspirado na implementação de Stephan T. Lavavej, achei que seria bom ter um make_unique que suporta extensões de matriz, está no github e eu gostaria de receber comentários sobre ele. Ele permite que você faça isso:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
Nathan Binkert
fonte