Quais usos existem para o “posicionamento novo”?

Alguém aqui já usou o “novo posicionamento” do C ++? Se sim, para quê? Parece-me que seria útil apenas em hardware mapeado em memory.

Posicionamento novo permite que você construa um object na memory que já está alocada.

Você pode querer fazer isso para otimizações (é mais rápido não realocar o tempo todo), mas é necessário reconstruir um object várias vezes. Se você precisar manter a realocação, pode ser mais eficiente alocar mais do que o necessário, mesmo que você não queira usá-lo ainda.

Devex dá um bom exemplo :

O C ++ padrão também suporta o operador new placement, que constrói um object em um buffer pré-alocado. Isso é útil ao construir um pool de memory, um coletor de lixo ou simplesmente quando desempenho e segurança de exceção são primordiais (não há perigo de falha de alocação desde que a memory já foi alocada e construir um object em um buffer pré-alocado leva menos tempo) :

char *buf = new char[sizeof(string)]; // pre-allocated buffer string *p = new (buf) string("hi"); // placement new string *q = new string("hi"); // ordinary heap allocation 

Você também pode querer ter certeza de que não haverá falha de alocação em uma determinada parte do código crítico (talvez você trabalhe em um marcapasso, por exemplo). Nesse caso, você gostaria de usar o novo canal.

Desalocação no posicionamento novo

Você não deve desalocar todos os objects que estão usando o buffer de memory. Em vez disso, você deve excluir [] apenas o buffer original. Você teria que chamar os destruidores diretamente de suas classs manualmente. Para uma boa sugestão sobre isso, consulte a FAQ da Stroustrup em: Existe uma “exclusão de veiculação” ?

Usamos isso com conjuntos de memory personalizados. Apenas um esboço:

 class Pool { public: Pool() { /* implementation details irrelevant */ }; virtual ~Pool() { /* ditto */ }; virtual void *allocate(size_t); virtual void deallocate(void *); static Pool::misc_pool() { return misc_pool_p; /* global MiscPool for general use */ } }; class ClusterPool : public Pool { /* ... */ }; class FastPool : public Pool { /* ... */ }; class MapPool : public Pool { /* ... */ }; class MiscPool : public Pool { /* ... */ }; // elsewhere... void *pnew_new(size_t size) { return Pool::misc_pool()->allocate(size); } void *pnew_new(size_t size, Pool *pool_p) { if (!pool_p) { return Pool::misc_pool()->allocate(size); } else { return pool_p->allocate(size); } } void pnew_delete(void *p) { Pool *hp = Pool::find_pool(p); // note: if p == 0, then Pool::find_pool(p) will return 0. if (hp) { hp->deallocate(p); } } // elsewhere... class Obj { public: // misc ctors, dtors, etc. // just a sampling of new/del operators void *operator new(size_t s) { return pnew_new(s); } void *operator new(size_t s, Pool *hp) { return pnew_new(s, hp); } void operator delete(void *dp) { pnew_delete(dp); } void operator delete(void *dp, Pool*) { pnew_delete(dp); } void *operator new[](size_t s) { return pnew_new(s); } void *operator new[](size_t s, Pool* hp) { return pnew_new(s, hp); } void operator delete[](void *dp) { pnew_delete(dp); } void operator delete[](void *dp, Pool*) { pnew_delete(dp); } }; // elsewhere... ClusterPool *cp = new ClusterPool(arg1, arg2, ...); Obj *new_obj = new (cp) Obj(arg_a, arg_b, ...); 

Agora você pode agrupar objects em uma única arena de memory, selecionar um alocador que é muito rápido, mas não faz desalocação, usa mapeamento de memory e qualquer outra semântica que você deseja impor escolhendo o conjunto e passando-o como um argumento para o posicionamento de um object novo operador.

É útil se você deseja separar a alocação da boot. O STL usa o novo posicionamento para criar elementos de contêiner.

Eu usei isso em programação em tempo real. Normalmente , não queremos executar nenhuma alocação dinâmica (ou desalocação) após o sistema ser iniciado, porque não há garantia de quanto tempo isso vai levar.

O que eu posso fazer é pré-alocar um grande pedaço de memory (grande o suficiente para conter qualquer quantidade de qualquer coisa que a class possa requerer). Então, uma vez que eu descubro em tempo de execução como construir as coisas, o posicionamento new pode ser usado para construir objects exatamente onde eu os quero. Uma situação que eu sei que usei foi para ajudar a criar um buffer circular heterogêneo.

Certamente não é para os fracos de coração, mas é por isso que eles fazem a syntax para isso meio que deformada.

Eu usei para construir objects alocados na pilha via alloca ().

plugue sem vergonha: eu escrevi sobre isso aqui .

Cabeça Geek: BINGO! Você entendeu totalmente – é exatamente para isso que é perfeito. Em muitos ambientes incorporados, as restrições externas e / ou o cenário de uso geral forçam o programador a separar a alocação de um object de sua boot. Resumidamente, o C ++ chama isso de “instanciação”; mas sempre que a ação do construtor deve ser invocada explicitamente sem alocação dinâmica ou automática, o posicionamento novo é a maneira de fazê-lo. É também a maneira perfeita de localizar um object C ++ global fixado ao endereço de um componente de hardware (E / S mapeada por memory) ou a qualquer object estático que, por qualquer motivo, deva residir em um endereço fixo.

Eu usei para criar uma class Variant (ou seja, um object que pode representar um único valor que pode ser um de vários tipos diferentes).

Se todos os tipos de valor suportados pela class Variant forem tipos POD (por exemplo, int, float, double, bool), então uma união de estilo C marcada é suficiente, mas se você quiser que alguns dos tipos de valor sejam objects C ++ ( por exemplo, std :: string), o recurso de união C não funcionará, pois os tipos de dados não-POD podem não ser declarados como parte de uma união.

Então, em vez disso, aloco uma matriz de bytes grande o suficiente (por exemplo, sizeof (the_largest_data_type_I_support)) e uso o novo posicionamento para inicializar o object C ++ apropriado nessa área quando a Variant é definida para manter um valor desse tipo. (E o posicionamento é deletado de antemão quando se muda de um tipo diferente de dados não-POD, é claro)

Também é útil quando você deseja reinicializar estruturas globais ou estaticamente alocadas.

O antigo modo C usava memset() para definir todos os elementos como 0. Você não pode fazer isso em C ++ devido a vtables e construtores de objects personalizados.

Então eu às vezes uso o seguinte

  static Mystruct m; for(...) { // re-initialize the structure. Note the use of placement new // and the extra parenthesis after Mystruct to force initialization. new (&m) Mystruct(); // do-some work that modifies m's content. } 

O posicionamento novo também é muito útil ao serializar (digamos com boost :: serialization). Em 10 anos de c ++ este é apenas o segundo caso que eu precisei de novo posicionamento para (terceiro se você include entrevistas :)).

É útil se você está construindo um kernel – onde você coloca o código do kernel que você lê no disco ou no pagetable? Você precisa saber para onde ir.

Ou em outras circunstâncias muito raras, como quando você tem um monte de espaço alocado e quer colocar algumas estruturas atrás umas das outras. Eles podem ser empacotados dessa maneira sem a necessidade do operador offsetof (). Existem outros truques para isso também.

Eu também acredito que algumas implementações de STL fazem uso do posicionamento new, como std :: vector. Eles alocam espaço para 2 ^ n elementos dessa maneira e não precisam sempre realocar.

Eu acho que isso não foi destacado por nenhuma resposta, mas outro bom exemplo e uso para o novo posicionamento é reduzir a fragmentação de memory (usando pools de memory). Isso é especialmente útil em sistemas incorporados e de alta disponibilidade. Neste último caso, é especialmente importante porque, para um sistema que precisa rodar 24/365 dias, é muito importante não ter fragmentação. Esse problema não tem nada a ver com memory leaks.

Mesmo quando uma implementação malloc muito boa é usada (ou function de gerenciamento de memory similar) é muito difícil lidar com a fragmentação por um longo tempo. Em algum momento, se você não gerenciar inteligentemente as chamadas de reserva / liberação de memory, poderá acabar com muitas pequenas lacunas que são difíceis de reutilizar (atribuir a novas reservas). Portanto, uma das soluções usadas neste caso é usar um conjunto de memorys para alocar antes a memory para os objects do aplicativo. Depois, sempre que precisar de memory para algum object, basta usar o novo posicionamento para criar um novo object na memory já reservada.

Desta forma, uma vez iniciado o seu aplicativo, você já terá toda a memory necessária reservada. Toda a nova reserva / liberação de memory vai para os pools alocados (você pode ter vários pools, um para cada class de object diferente). Nenhuma fragmentação de memory acontece neste caso, já que não haverá lacunas e seu sistema pode ser executado por períodos muito longos (anos) sem sofrer fragmentação.

Eu vi isso na prática, especialmente para o VxWorks RTOS, já que seu sistema de alocação de memory padrão sofre muito com a fragmentação. Portanto, alocar memory através do método padrão new / malloc era basicamente proibido no projeto. Todas as reservas de memory devem ir para um conjunto de memorys dedicado.

É usado por std::vector<> porque std::vector<> tipicamente aloca mais memory do que objects no vector<> .

Eu usei para armazenar objects com arquivos mapeados de memory.
O exemplo específico era um database de imagens que processava um grande número de imagens grandes (mais do que cabia na memory).

Eu usei-o para criar objects baseados na memory contendo mensagens recebidas da rede.

Eu vi isso usado como um leve truque de desempenho para um ponteiro de “tipo dynamic” (na seção “Under the Hood”):

Mas aqui está o truque complicado que eu usei para obter um desempenho rápido para tipos pequenos: se o valor que está sendo mantido couber dentro de um vazio *, eu não me incomodo em alocar um novo object, eu o forcei no próprio ponteiro usando posicionamento novo .

Geralmente, o posicionamento novo é usado para se livrar do custo de alocação de um ‘novo normal’.

Outro cenário em que usei é um lugar onde eu queria ter access ao ponteiro para um object que ainda estava para ser construído, para implementar um singleton por documento.

Pode ser útil ao usar a memory compartilhada, entre outros usos … Por exemplo: http://www.boost.org/doc/libs/1_51_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions. conditions_anonymous_example

O único local em que corri o problema é em contêineres que alocam um buffer contíguo e, em seguida, o preenchem com objects conforme necessário. Como mencionado, o std :: vector pode fazer isso, e eu sei que algumas versões do MFC CArray e / ou do CList fizeram isso (porque é onde eu o fiz pela primeira vez). O método de alocação excessiva do buffer é uma otimização muito útil, e o posicionamento novo é praticamente a única maneira de construir objects nesse cenário. Também é usado às vezes para construir objects em blocos de memory alocados fora do seu código direto.

Eu usei-o em uma capacidade similar, embora não surja frequentemente. É uma ferramenta útil para a checkbox de ferramentas C ++, no entanto.

Os mecanismos de script podem usá-lo na interface nativa para alocar objects nativos de scripts. Veja Angelscript (www.angelcode.com/angelscript) para exemplos.

Na verdade, é necessário implementar qualquer tipo de estrutura de dados que aloque mais memory do que o mínimo necessário para o número de elementos inseridos (isto é, qualquer coisa que não seja uma estrutura vinculada que aloque um nó por vez).

Tome contêineres como unordered_map , vector ou deque . Todos eles alocam mais memory do que é minimamente necessária para os elementos que você inseriu até o momento para evitar a necessidade de uma alocação de heap para cada inserção. Vamos usar o vector como o exemplo mais simples.

Quando você faz:

 vector vec; // Allocate memory for a thousand Foos: vec.reserve(1000); 

… isso na verdade não constrói mil Foos. Ele simplesmente aloca / reserva memory para eles. Se o vector não usasse o posicionamento novo aqui, ele seria o padrão de construção Foos todo o lugar, assim como ter que invocar seus destruidores, mesmo para elementos que você nunca inseriu em primeiro lugar.

Alocação! = Construção, Libertação! = Destruição

Em geral, para implementar muitas estruturas de dados como as acima, você não pode tratar a alocação de memory e a construção de elementos como uma coisa indivisível, e você também não pode tratar de liberar memory e destruir elementos como uma coisa indivisível.

Tem que haver uma separação entre essas idéias para evitar desnecessariamente invocar construtores e destruidores desnecessariamente esquerda e direita, e é por isso que a biblioteca padrão separa a idéia de std::allocator (que não constrói ou destrói elementos quando aloca / libera memory *) longe dos contêineres que o utilizam, que constroem manualmente elementos usando o posicionamento new e destruindo manualmente os elementos usando invocações explícitas de destruidores.

  • Eu odeio o design do std::allocator mas esse é um assunto diferente que eu vou evitar reclamar. 😀

De qualquer forma, eu costumo usá-lo muito desde que eu escrevi uma série de contentores C ++ compatíveis com o padrão de uso geral que não poderiam ser construídos em termos dos existentes. Incluído entre eles está uma pequena implementação de vetor que construí há algumas décadas atrás para evitar alocações de heap em casos comuns, e uma trie de memory eficiente (não aloca um nó de cada vez). Em ambos os casos, não consegui implementá-los usando os contêineres existentes e, por isso, precisei usar o placement new para evitar que os construtores e destruidores fossem invocados de forma desnecessária para a esquerda e para a direita.

Naturalmente, se você já trabalhou com alocadores personalizados para alocar objects individualmente, como uma lista livre, você também geralmente gostaria de usar o placement new , como este (exemplo básico que não se preocupa com segurança de exceção ou RAII):

 Foo* foo = new(free_list.allocate()) Foo(...); ... foo->~Foo(); free_list.free(foo); 

Veja o arquivo fp.h no projeto xll em http://xll.codeplex.com Ele soluciona o problema de “descomplicado com o compilador” para matrizes que gostam de carregar suas dimensões com eles.

 typedef struct _FP
 {
     linhas int curtas não assinadas;
     colunas int curtas não assinadas;
     matriz dupla [1];  / * Na verdade, array [linhas] [colunas] * /
 } FP;

Aqui está o uso matador para o construtor C ++ no local: o alinhamento a uma linha de cache, bem como outros poderes de 2 limites. Aqui está o meu algoritmo de alinhamento de ponteiro ultrarrápido para qualquer potência de 2 limites com 5 ou menos instruções de ciclo único :

 /* Quickly aligns the given pointer to a power of two boundary IN BYTES. @return An aligned pointer of typename T. @brief Algorithm is a 2's compliment trick that works by masking off the desired number in 2's compliment and adding them to the pointer. @param pointer The pointer to align. @param boundary_byte_count The boundary byte count that must be an even power of 2. @warning Function does not check if the boundary is a power of 2! */ template  inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) { uintptr_t value = reinterpret_cast(pointer); value += (((~value) + 1) & (boundary_byte_count - 1)); return reinterpret_cast(value); } struct Foo { Foo () {} }; char buffer[sizeof (Foo) + 64]; Foo* foo = new (AlignUp (buffer, 64)) Foo (); 

Agora, isso não coloca um sorriso no seu rosto (:-). Eu ♥♥♥ C ++ 1x