Pergunta

Alguém aqui já usado C ++ 's 'posicionamento novo'? Se assim for, para quê? Parece-me que ele só seria útil no hardware de memória mapeada.

Foi útil?

Solução

Placement novo permite que você construa um objeto na memória que já está alocado.

Você pode querer fazer isso para otimização quando você precisa para construir múltiplas instâncias de um objeto, e mais rápido não é a re-alocar memória cada vez que você precisa de uma nova instância. Em vez disso, pode ser mais eficiente para realizar uma única alocação de um bloco de memória que pode conter vários objetos, mesmo que você não quer usar tudo isso ao mesmo tempo.

DevX dá um bom exemplo :

padrão C ++ também suporta colocação novo operador, o qual constrói uma objeto em um tampão de pré-atribuído. este é útil quando a construção de um pool de memória, um coletor de lixo ou simplesmente quando desempenho e segurança de exceção são fundamental (não há perigo de falha de alocação já que a memória já foi atribuído, e construção de um objeto em uma tampão pré-alocada 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 a certeza não pode haver falha na atribuição de uma determinada parte do código crítica (por exemplo, no código executado por um pacemaker). Nesse caso, você gostaria de alocar a memória mais cedo, em seguida, usar posicionamento novo dentro da seção crítica.

Desalocação na colocação de novo

Você não deve liberar cada objeto que está usando o buffer de memória. Em vez disso você deve excluir [] somente o buffer de origem. Você teria que, em seguida, chamar os destruidores de suas classes manualmente. Para uma boa sugestão sobre este assunto, consulte FAQ do Stroustrup em: Existe uma "colocação de exclusão" ?

Outras dicas

Nós usá-lo com pools de memória personalizado. 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 juntos em uma única arena de memória, selecione um alocador que é muito rápido, mas não faz deallocation, mapeamento de memória uso e qualquer outra semântica deseja impor escolhendo a piscina e passá-lo como um argumento para colocação novo operador de um objeto.

É útil se você quiser separar alocação de inicialização. STL usa posicionamento novo para criar elementos de contêiner.

Eu usei-o na programação em tempo real. Nós normalmente não quiser executar qualquer alocação dinâmica (ou deallocation) depois que o sistema é iniciado, porque não há nenhuma garantia de quanto tempo isso vai levar.

O que posso fazer é pré-alocar um grande pedaço de memória (suficientemente grande para conter qualquer quantidade de seja lá o que a classe pode exigir). Então, uma vez que eu descobrir em tempo de execução como construir as coisas, a colocação de novo pode ser usado para construir objetos exatamente onde eu quero que eles. Uma situação que eu sei que eu usei na era ajudar a criar um heterogêneo circular tampão .

Ela certamente não é para os fracos de coração, mas é por isso que eles fazem a sintaxe para kinda gnarly.

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

plug descarado: eu escrevi sobre ele aqui .

Cabeça Geek: BINGO! Você entendeu totalmente - que é exatamente o que é perfeito para. Em muitos ambientes incorporados, restrições externas e / ou as forças globais de uso do cenário o programador para separar a atribuição de um objeto a partir de sua inicialização. Agrupados, C ++ chama isso de "instanciação"; mas sempre que a ação do construtor deve ser explicitamente invocado sem dinâmico ou alocação automática, posicionamento novo é a maneira de fazê-lo. É também a maneira perfeita para localizar um objeto global C ++ que é fixado para o endereço de um componente de hardware (memória mapeada I / O), ou por qualquer objeto estático que, por qualquer motivo, deve residir em um endereço fixo.

Eu usei-o para criar uma classe Variant (ou seja, um objeto que pode representar um único valor que pode ser um de um número de diferentes tipos).

Se todo o valor-tipos suportados pela classe Variant são tipos POD (por exemplo, int, float, double, bool), em seguida, um marcado C-style união é suficiente, mas se você quiser algum do valor-tipos para ser objetos C ++ (por exemplo, std :: string), o recurso C sindicato não vai fazer, como tipos de dados não-POD não podem ser declarados como parte de uma união.

Então, ao invés eu alocar uma matriz de bytes que é grande o suficiente (por exemplo, sizeof (the_largest_data_type_I_support)) e colocação uso novo para inicializar o objeto apropriado C ++ nessa área quando o Variant está definido para conter um valor desse tipo. (E colocação excluir de antemão quando se muda de distância de um tipo de dados não-POD diferente, é claro)

Também é útil quando você quer re-inicializar estruturas globais ou alocados estaticamente.

A forma C velho estava usando memset() para definir todos os elementos para 0. Você não pode fazer isso em C ++, devido à vtables e construtores de objetos 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.
 }

É útil se você está construindo um kernel - onde você colocar o código do kernel você ler a partir de disco ou o pagetable? Você precisa saber para onde saltar.

Ou, em outras circunstâncias, muito raros, como quando você tem um monte de vaga no quarto e quer colocar algumas estruturas atrás do outro. Eles podem ser embalados desta forma, sem a necessidade do operador offsetof (). Há outros truques para isso também, apesar de tudo.

Eu também acredito que algumas implementações STL fazer uso de posicionamento novo, como std :: vector. Eles alocar espaço para 2 ^ n elementos dessa forma e não precisa sempre realloc.

Placement novo também é muito útil quando serializadas (digamos com boost :: serialização). Em 10 anos de c ++ este é apenas o segundo caso eu precisava posicionamento novo para (terceiro, se você incluir entrevistas :)).

Eu acho que isso não tem sido destacada por qualquer resposta, mas um outro exemplo bom e uso para o nova colocação é reduzir a fragmentação de memória (usando pools de memória). Isto é especialmente útil em sistemas de disponibilidade incorporados e elevados. Neste último caso, é especialmente importante porque para um sistema que tem que correr 24/365 dias, é muito importante ter nenhuma fragmentação. Este problema não tem nada a ver com o vazamento de memória.

Mesmo quando uma muito boa aplicação malloc é usado (ou função de gerenciamento de memória similar) é muito difícil lidar com a fragmentação por um longo tempo. Em algum momento, se você não gerenciar inteligentemente a reserva de memória / release chama você pode acabar com um monte de pequenas lacunas que são difíceis de reutilização (atribuir a novas reservas). Então, uma das soluções que são usados ??neste caso é usar um pool de memória para alocar antes da mão a memória para os objetos da aplicação. Depois de-alas cada vez que você precisa de memória por algum objeto que você acabou de usar o nova colocação para criar um novo objeto na memória já reservados.

Desta forma, uma vez que seu aplicativo é iniciado você já tem toda a memória necessária reservados. Toda a nova reserva de memória / release vai para as piscinas alocados (você pode ter várias piscinas, uma para cada classe de objeto diferente). Sem a fragmentação da memória acontece neste caso, já que não haverá lacunas e seu sistema pode funcionar por longos períodos (anos) sem sofrer de fragmentação.

Eu vi isso na prática especialmente para o VxWorks RTOS desde o seu sistema de alocação de memória padrão sofre muito com a fragmentação. Assim, a alocação de memória através do método padrão novo / malloc foi basicamente proibida no projeto. Todas as reservas de memória deve ir a um pool de memória dedicada.

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

Eu já vi isso usado como um ligeiro corte desempenho para um ponteiro "tipo dinâmico" (na seção "Under the Hood"):

Mas aqui está o truque complicado que usei para obter um desempenho rápido para pequenos tipos: se o valor a ser realizada pode caber dentro de um * vazio, eu realmente não incomoda atribuição de um novo objeto, eu forçá-lo para o ponteiro em si usando posicionamento novo.

Ele é usado por std::vector<> porque std::vector<> normalmente aloca mais memória do que há objects na vector<>.

É realmente tipo de necessária para implementar qualquer tipo de estrutura de dados que aloca mais memória do que minimamente necessário para o número de elementos inseridos (ou seja, qualquer coisa diferente de uma estrutura ligada que aloca um nó de cada vez).

Tome recipientes como unordered_map, vector, ou deque. Estes todos alocar mais memória do que é minimamente necessário para os elementos que você já inseridas até agora para evitar que requer uma alocação de pilha para cada inserção. Vamos uso vector como o exemplo mais simples.

Quando você faz:

vector<Foo> vec;

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

... que não realmente construir mil Foos. Ele simplesmente Aloca memória / reservas para eles. Se vector não utilizar a colocação de novo aqui, seria Foos em todo o lugar, bem como ter de invocar os seus destruidores mesmo para elementos que você nunca sequer inseridos em primeiro lugar, a construção padrão.

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

Apenas um modo geral para implementar muitas estruturas de dados como o acima, você não pode tratar a alocação de memória e construção de elementos como uma coisa indivisível, e você também não podem memória libertação deleite e elementos destruindo como uma coisa indivisível.

Tem de haver uma separação entre essas idéias para evitar redundante 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 construir ou destruir elementos quando se aloca / memória liberta *) longe dos recipientes que o utilizam que não construir manualmente elementos usando posicionamento novo e destruir manualmente elementos usando invocações explícitas de destruidores.

  • Eu odeio o projeto de std::allocator mas isso é um assunto diferente Vou evitar ranting sobre. :-D

De qualquer forma, eu tendem a usá-lo muito desde que eu tenha escrito uma série de recipientes de uso geral compatível com o padrão C ++ que não poderiam ser construídas em termos das já existentes. Entre eles é uma pequena aplicação vector eu construí um par de décadas atrás para evitar alocações de heap em casos comuns, e uma trie memória-eficiente (não aloca um nó de cada vez). Em ambos os casos, eu não poderia realmente implementá-las usando os recipientes existentes, e então eu tive que usar placement new para evitar redundante invocar construtores e destruidores sobre coisas esquerda desnecessária e direita.

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

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

Eu usei-o para criar objetos com base na memória contendo mensagens recebidas a partir da rede.

Geralmente, a colocação de novo é usado para se livrar de custo de alocação de um 'normal novo'.

Outro cenário onde eu costumava é um lugar onde eu queria ter acesso ao ponteiro para um objeto que foi ainda a ser construído, para implementar um singleton por documento.

O único lugar que eu executar toda é em recipientes que alocar um buffer contíguo e, em seguida, preenchê-lo com objetos conforme necessário. Como mencionado, std :: vector pode fazer isso, e eu sei que algumas versões do MFC CArray e / ou CList fez isso (porque é onde eu primeiro correu através dele). O método excesso de alocação de buffer é uma otimização muito útil, e colocação de novo é praticamente a única forma de objetos construto em que cenário. Ele também é usado às vezes para objetos construto em blocos de memória alocados fora do seu código direta.

Eu usei-o em uma capacidade semelhante, embora ele não vem acima frequentemente. É uma ferramenta útil para a caixa de ferramentas C ++, no entanto.

motores de script pode usá-lo na interface nativa para alocar objetos nativos de scripts. Veja AngelScript (www.angelcode.com/angelscript) para exemplos.

Veja o arquivo fp.h no projeto XLL em http://xll.codeplex.com ele resolve o "chumminess injustificada com o compilador" problema para arrays que gostam de levar suas dimensões ao redor com eles.

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

Aqui está o uso do assassino para o C ++ construtor no local: alinhar a uma linha de cache, bem como outras potências de 2 limites. Aqui está meu algoritmo de alinhamento ponteiro ultra-rápido para qualquer poder 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, não que basta colocar um sorriso em seu rosto (:-). I ??? C ++ 1x

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top