Que usos existem para o "posicionamento novo"?


411

Alguém aqui já usou o "posicionamento novo" do C ++? Se sim, para quê? Parece-me que só seria útil em hardware mapeado na memória.


14
Esta é apenas a informação que eu estava procurando, para chamar construtores de objetos em conjuntos de memória alocada por impulso. Esperar que essas palavras-chave tornem mais fácil encontrar alguém no futuro.
Sideshow Bob

2
É usado no artigo da Wikipedia C ++ 11 no construtor de uma união.
HelloGoodbye 5/11

@HelloGoodbye, interessante! No artigo que você vinculou, por que você não pode simplesmente p = ptusar e usar o operador de atribuição em Pointvez de o fazer new(&p) Point(pt)? Eu me pergunto as diferenças entre os dois. O primeiro chamaria operator=Point, enquanto o último chama o construtor de cópias de Point? mas ainda não estou muito claro por que um é melhor que o outro.
Andrei-Niculae Petre 28/11

@ Andrei-NiculaePetre Eu não usei o novo posicionamento, mas acho que você deve usá-lo - junto com o construtor de cópias - se não tiver um objeto dessa classe no momento, caso contrário, use o operador de atribuição de cópias. A menos que a classe seja trivial; então não importa qual deles você usa. O mesmo vale para a destruição do objeto. Não conseguir lidar com isso adequadamente para classes não triviais pode muito provavelmente levar a um comportamento estranho e até causar um comportamento indefinido em algumas situações.
HelloGoodbye 28/11

@ Andrei-NiculaePetre Na verdade, acho o exemplo no artigo da Wikipedia bastante ruim, pois supõe que nenhum objeto anterior exista e que eles precisam construir um. Este não é o caso se U::operator=acabou de ser chamado.
hellogoodbye

Respostas:


365

O posicionamento new permite construir um objeto na memória que já está alocado.

Você pode fazer isso para otimização quando precisar criar várias instâncias de um objeto, e é mais rápido não alocar memória novamente sempre que precisar de uma nova instância. Em vez disso, pode ser mais eficiente executar uma alocação única para um pedaço de memória que pode conter vários objetos, mesmo que você não queira usá-lo de uma só vez.

O DevX dá um bom exemplo :

O C ++ padrão também suporta a colocação de novo operador, que constrói um objeto em um buffer pré-alocado. Isso é útil ao criar um pool de memória, um coletor de lixo ou simplesmente quando a segurança do desempenho e das exceções é fundamental (não há risco de falha na alocação, pois a memória já foi alocada e a construção de um objeto 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 ter certeza de que não pode haver falha na alocação em uma determinada parte do código crítico (por exemplo, no código executado por um marcapasso). Nesse caso, você deseja alocar memória mais cedo e, em seguida, use o posicionamento new na seção crítica.

Desalocação na veiculação novo

Você não deve desalocar todos os objetos que estão usando o buffer de memória. Em vez disso, você deve excluir [] apenas o buffer original. Você teria que chamar os destruidores de suas classes manualmente. Para uma boa sugestão, consulte as Perguntas frequentes do Stroustrup sobre: Existe uma "exclusão de canal" ?


54
Ele não foi preterido, pois você precisa desse recurso para implementar eficientemente objetos de contêiner (como vetor). Se você não estiver construindo seu próprio contêiner, não precisará usar esse recurso.
Martin York

26
Também é muito importante lembrar de #include <memória>, caso contrário, você pode executar em algumas dores de cabeça terríveis em algumas plataformas que não reconhecem automaticamente posicionamento novo
Ramon Zarazua B.

22
Estritamente, é um comportamento indefinido chamar delete[]o charbuffer original . O uso da veiculação newencerrou a vida útil dos charobjetos originais , reutilizando seu armazenamento. Se você chamar delete[] bufo tipo dinâmico do (s) objeto (s) apontado (s) para não corresponder mais ao tipo estático, você terá um comportamento indefinido. É mais consistente usar operator new/ operator deletealocar memória bruta pretendida para uso por posicionamento new.
CB Bailey

31
Eu definitivamente pular sobre o uso da pilha em um pacemaker :-)
Eli Bendersky

15
@RamonZarazua Cabeçalho errado, é #include <new>.
precisa saber é o seguinte

63

Nós o usamos com conjuntos de memória 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 objetos em uma única arena de memória, selecionar um alocador muito rápido, mas sem desalocação, usar mapeamento de memória e qualquer outra semântica que desejar impor, escolhendo o pool e passando-o como argumento para a localização de um objeto novo operador.


1
Sim. Ficamos bastante espertos quanto a isso, mas é fora de tópico para esta pergunta.
Don Wakefield

2
@jdkoftinoff você tem algum link para um exemplo de código real? parece muito interessante para mim!
Victor Victor

@DonWakefield Como você lida com o alinhamento nesta piscina? Você não deveria passar o alinhamento como argumento para allocate()algum lugar?
Mikhail Vasilyev

1
@MikhailVasilyev, em uma implementação real, é claro que você lidaria com isso. Apenas código de exemplo.
Don Wakefield

e se o canal for um endereço inválido, digamos 0x0?
Charlie

51

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


35

Eu usei na programação em tempo real. Normalmente , não queremos realizar nenhuma alocação dinâmica (ou desalocação) após a inicialização do sistema, porque não há garantia de quanto tempo isso levará.

O que posso fazer é pré-alocar um grande pedaço de memória (grande o suficiente para armazenar qualquer quantidade que a classe exigir). Então, quando eu descobrir em tempo de execução como construir as coisas, o posicionamento new pode ser usado para construir objetos exatamente onde eu os quero. Uma situação em que sei que o usei foi ajudar a criar um buffer circular heterogêneo .

Certamente não é para os fracos de coração, mas é por isso que eles criam a sintaxe para isso de maneira meio esquisita.


Olá TED, você poderia compartilhar mais sobre a solução que possui. Estou pensando em uma solução pré-alocada, mas não tenho muito progresso. Agradeço antecipadamente!
Viet

1
Bem, o código de buffer circular hetrogênico real foi realmente a parte complicada de acertar. O novo palácio parece um pouco horrível, mas, em comparação, não foi problema.
TED

26

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

plugue sem vergonha: eu escrevi sobre isso aqui .


artigo interessante, mas não sei se entendi a vantagem de usar isso novamente boost::array. Você pode expandir um pouco isso?
GrahamS

boost :: array exige que o tamanho da matriz seja uma constante em tempo de compilação. Isso não tem essa limitação.
Ferruccio

2
@Ferruccio Isso é muito legal, notei que sua macro é um pouco insegura, a saber, o tamanho pode ser uma expressão. Se x + 1 for passado, por exemplo, você o expandirá para sizeof (type) * x + 1, o que estaria incorreto. Você precisa ajustar sua macro para torná-la mais segura.
21412 Benj

Usar o alloca me parece perigoso se uma exceção for lançada, pois você deve chamar os destruidores de todos os seus objetos.
Cashcow

14

Geek principal: BINGO! Você entendeu totalmente - é exatamente para isso que é perfeito. Em muitos ambientes incorporados, restrições externas e / ou o cenário de uso geral força o programador a separar a alocação de um objeto da sua inicialização. Agrupados, o C ++ chama isso de "instanciação"; mas sempre que a ação do construtor deve ser explicitamente invocada SEM alocação dinâmica ou automática, o posicionamento new é a maneira de fazê-lo. É também a maneira perfeita de localizar um objeto C ++ global fixado no endereço de um componente de hardware (E / S mapeada na memória) ou em qualquer objeto estático que, por qualquer motivo, precise residir em um endereço fixo.


12

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

Se todos os tipos de valor suportados pela classe Variant são do tipo POD (por exemplo, int, float, double, bool), uma união no estilo C marcada é suficiente, mas se você deseja que alguns tipos de valor sejam objetos C ++ ( por exemplo, std :: string), o recurso de união C não funciona, pois tipos de dados que não são POD podem não ser declarados como parte de uma união.

Então, em vez disso, aloco uma matriz de bytes que seja grande o suficiente (por exemplo, sizeof (the_largest_data_type_I_support)) e uso a colocação new para inicializar o objeto C ++ apropriado nessa área quando a Variant estiver configurada para conter um valor desse tipo. (E o posicionamento é excluído antecipadamente ao sair de um tipo de dados diferente de POD, é claro)


Erm, tipos de dados não POD podem ser declarados dentro de uma união, desde que você forneça um ctor de união - e ei - que provavelmente usaria posicionamentonew para inicializar sua subclasse não-POD. Ref: stackoverflow.com/a/33289972/2757035 Reinventar esta roda usando uma matriz de bytes arbitrariamente grande é uma peça impressionante de acrobacias, mas parece totalmente desnecessária. Então, o que eu perdi? :)
underscore_d

6
Você perdeu todas as versões do C ++ anteriores ao C ++ 11, que em muitos casos ainda precisam ser suportadas. :)
Jeremy Friesner

10

O posicionamento new também é muito útil ao serializar (por exemplo, com boost :: serialization). Em 10 anos de c ++, este é apenas o segundo caso para o qual eu precisava de um novo posicionamento (terceiro, se você incluir entrevistas :)).


9

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

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

Às vezes eu 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.
 }

1
Você não precisaria fazer uma destruição correspondente antes de reiniciá-la dessa maneira?
Chefe Geek

[Editado para ortografia] Normalmente - você faz. Mas, às vezes, quando você sabe que a classe não aloca memória ou outros recursos (ou os desalocou externamente - por exemplo, quando você usa conjuntos de memória), pode usar esta técnica. Ele garante que os ponteiros da tabela v não sejam substituídos. # Nimrodm 16 hours ago
nimrodm 06/04

1
Mesmo em C, o uso de todos os bits para 0 é garantido apenas para produzir uma representação de 0 para tipos integrais, não para outros tipos (o ponteiro nulo pode ter uma representação diferente de zero).
precisa saber é o seguinte

@curiousguy - para tipos primitivos, você está correto (isso tornará o programa previsível, o que é uma vantagem quando se trata de depuração). No entanto, os tipos de dados C ++ terão seu construtor executado (no local) e serão inicializados corretamente.
Nimrodm

9

Acho que isso não foi destacado por nenhuma resposta, mas outro bom exemplo e uso para o novo canal é reduzir a fragmentação da memória (usando conjuntos de memória). Isso é especialmente útil em sistemas embarcados e de alta disponibilidade. Neste último caso, é especialmente importante porque, para um sistema que precisa ser executado 24/365 dias, é muito importante não haver fragmentação. Esse problema não tem nada a ver com vazamento de memória.

Mesmo quando uma implementação malloc muito boa é usada (ou função de gerenciamento de memória semelhante), é muito difícil lidar com a fragmentação por um longo tempo. Em algum momento, se você não gerenciar de maneira inteligente as chamadas de reserva / liberação de memória, poderá acabar com muitas pequenas lacunas difíceis de reutilizar (atribuir a novas reservas). Portanto, uma das soluções usadas nesse caso é usar um pool de memória para alocar antes da mão a memória para os objetos de aplicativo. Depois de cada vez que você precisar de memória para algum objeto, basta usar o novo posicionamento para criar um novo objeto na memória já reservada.

Dessa forma, uma vez iniciado o aplicativo, você já terá toda a memória necessária reservada. Toda a nova reserva / liberação de memória vai para os conjuntos alocados (você pode ter vários conjuntos, um para cada classe de objeto diferente). Nenhuma fragmentação de memória ocorre neste caso, pois não haverá falhas e seu sistema poderá funcionar por períodos muito longos (anos) sem sofrer fragmentação.

Vi isso na prática especialmente para o VxWorks RTOS, pois seu sistema de alocação de memória padrão sofre muito com a fragmentação. Portanto, alocar memória pelo método new / malloc padrão era basicamente proibido no projeto. Todas as reservas de memória devem ir para um conjunto de memórias dedicado.


9

É realmente necessário implementar qualquer tipo de estrutura de dados que aloque mais memória do que o mínimo necessário para o número de elementos inseridos (ou seja, qualquer coisa que não seja uma estrutura vinculada que aloque um nó por vez).

Tomar como recipientes unordered_map, vectorou deque. Todos eles alocam mais memória do que é minimamente necessário para os elementos que você inseriu até o momento, para evitar a necessidade de uma alocação de heap para cada inserção única. Vamos usar vectorcomo o exemplo mais simples.

Quando você faz:

vector<Foo> vec;

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

... que na verdade não constrói mil Foos. Simplesmente aloca / reserva memória para eles. Se vectornão usasse o posicionamento novo aqui, seria uma construção padrão em Foostodo o lugar, além de precisar invocar seus destruidores, mesmo para elementos que você nunca inseriu em primeiro lugar.

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

De um modo geral, para implementar muitas estruturas de dados como a acima, você não pode tratar a alocação de memória e a construção de elementos como uma coisa indivisível e da mesma forma não pode tratar a liberação de memória e a destruição de elementos como uma coisa indivisível.

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

  • Eu odeio o design, std::allocatormas esse é um assunto diferente sobre o qual evitarei falar. :-D

De qualquer maneira, costumo usá-lo bastante desde que escrevi vários contêineres C ++ compatíveis com o padrão de uso geral que não puderam ser construídos em termos dos existentes. Entre elas, está uma pequena implementação vetorial que eu construí algumas décadas atrás, para evitar alocações de heap em casos comuns, e uma tentativa com eficiência de memória (não aloca um nó por vez). Nos dois casos, não consegui realmente implementá-los usando os contêineres existentes e, portanto, tive que usar placement newpara evitar invocar supérfluos construtores e destruidores em coisas desnecessárias, esquerda e direita.

Naturalmente, se você trabalha com alocadores personalizados para alocar objetos individualmente, como uma lista gratuita, geralmente também deseja usá-lo placement new, como este (exemplo básico que não se preocupa com a exceção de segurança ou RAII):

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

8

É útil se você estiver construindo um kernel - onde você coloca o código do kernel que lê do disco ou da paginação? Você precisa saber para onde pular.

Ou, em outras circunstâncias, muito raras, como quando você tem muito espaço alocado e deseja colocar algumas estruturas atrás uma da outra. 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 STL fazem uso de novas colocações, como std :: vector. Eles alocam espaço para 2 ^ n elementos dessa maneira e nem sempre precisam realocar.


Reduzindo as alocações de memória é uma razão primária para usá-lo, bem como "truques" como carregamento de objetos fora do disco
lefticus

Não conheço nenhum kernel escrito em C ++; a maioria dos kernels são escritos em C. reta
Adam Rosenfield

8
O sistema operacional com o qual eu aprendi OS básico é escrito em C ++: sweb.sourceforge.net
mstrobl

8

É usado std::vector<>porque std::vector<>normalmente aloca mais memória do que há objectsno vector<>.


7

Eu usei para armazenar objetos com arquivos mapeados na memória.
O exemplo específico foi um banco de dados de imagens que processou um grande número de imagens grandes (mais do que poderia caber na memória).


7

Eu já vi isso como um pequeno hack de desempenho para um ponteiro de "tipo dinâmico" (na seção "Sob o capô"):

Mas aqui está o truque que eu usei para obter um desempenho rápido para tipos pequenos: se o valor mantido pode caber dentro de um vazio *, na verdade eu não me importo em alocar um novo objeto, forço-o para o ponteiro usando o posicionamento new .


O que significa se o valor mantido pode caber dentro de um vazio * ? É sempre possível atribuir qualquer tipo de ponteiro para anular *. Você pode nos mostrar algum exemplo?
Anurag86

@ anurag86: Na minha máquina de 64 bits, a void*leva 8 bytes. É um pouco tolo apontar oito bytes void*para um byte bool. Mas é perfeitamente possível sobrepor boolo void*, como um union { bool b; void* v }. Você precisa de alguma maneira de saber que a coisa que você chamou de void*na verdade é a bool(ou a short, ou a floatetc.). O artigo ao qual vinculei descreve como fazer isso. E, para responder à pergunta original, a veiculação newé o recurso usado para criar um bool(ou outro tipo) onde a void*é esperado (as conversões são usadas para obter / modificar posteriormente o valor).
Max Lybbert

@ anurag86: Não é a mesma coisa, mas você pode estar interessado em indicadores com tags ( en.wikipedia.org/wiki/Tagged_pointer ).
Max Lybbert

6

Usei-o para criar objetos com base na memória contendo mensagens recebidas da rede.


5

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

Outro cenário em que o usei é um local em que eu queria ter acesso ao ponteiro para um objeto que ainda deveria ser construído, para implementar um singleton por documento.



4

O único lugar que eu encontrei é em contêineres que alocam um buffer contíguo e o preenchem com os objetos, conforme necessário. Como mencionado, o std :: vector pode fazer isso, e eu sei que algumas versões do MFC CArray e / ou CList fizeram isso (porque é onde eu o encontrei pela primeira vez). O método de superalocação de buffer é uma otimização muito útil, e a colocação de novo é praticamente a única maneira de construir objetos nesse cenário. Às vezes, também é usado para construir objetos em blocos de memória alocados fora do seu código direto.

Eu o usei em uma capacidade semelhante, embora não apareça com frequência. É uma ferramenta útil para a caixa de ferramentas C ++.


4

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


3

Consulte o arquivo fp.h no projeto xll em http://xll.codeplex.com. Ele resolve o problema de "chulminess injustificado com o compilador" para matrizes que gostam de carregar suas dimensões com elas.

typedef struct _FP
{
    unsigned short int rows;
    unsigned short int columns;
    double array[1];        /* Actually, array[rows][columns] */
} FP;

2

Aqui está o uso matador do construtor C ++ in-loco: alinhando-se a uma linha de cache, além de outras potências de 2 limites. Aqui está o meu algoritmo de alinhamento de ponteiro ultra-rá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 <typename T = char>
inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) {
  uintptr_t value = reinterpret_cast<uintptr_t>(pointer);
  value += (((~value) + 1) & (boundary_byte_count - 1));
  return reinterpret_cast<T*>(value);
}

struct Foo { Foo () {} };
char buffer[sizeof (Foo) + 64];
Foo* foo = new (AlignUp<Foo> (buffer, 64)) Foo ();

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.