Precisamos separar alocação e inicialização de algum armazenamento de heap. Infelizmente, o código do cliente usa delete p;
para excluir o ponteiro. Se tivéssemos controle da exclusão, poderíamos alocar com ::operator new (sizeof(T), std::align_val_t(alignof(T)))
e então usar posicionamento new
e o global correspondente ::operator delete
. Mas (se entendi corretamente) delete
pode chamar T::operator delete
, potencialmente exigindo uma alocação que veio de T::operator new
, com uma ordem de preferência complicada entre os diferentes argumentos possíveis para T::operator new
e T::operator delete
se houver múltiplas sobrecargas.
Existe algo assim std::allocate_as_if_by_new<T>()
que fará exatamente a mesma alocação que new T(...)
eu faria, mas sem inicializar, ou pelo menos garantirá que (após a inicialização) delete p;
irá desalocá-la corretamente?
Não. O melhor que você pode fazer é usar algo como a expressão handle.
O idioma handle é quando você passa um valor que não é um ponteiro real para seus dados reais. Você pode permitir que ele seja deletado fazendo com que o handle tenha um destrutor que você controla.
então retorne
new Forwarder{ pRealObject }
de suas APIs.Eles são
delete
oForwarder
objeto, que faz a limpeza adequada no seu objeto subjacente realpImpl
. Se ele tiver uma tabela de função virtual, suaForwarder
implementação apenas salta tudo para mexer compImpl
isso.E se não houver uma tabela de funções virtuais (por exemplo, funções livres em um ADT), você ensina esses ADTs a converter os argumentos para
Forwarder*
então usápImpl
-los.Pode ajudar se você usar uma classe diferente para o público
Interface
do que sua internapImpl
para evitar chamar APIs públicas que esperam identificadores com o tipo interno e vice-versa; isso requer alguma duplicação de código ou alguma tolice de modelo.Em um ambiente mais parecido com C, o idioma do identificador se parece com:
onde você retorna um ponteiro para uma estrutura opaca do seu código de criação.
Resposta curta: Não.
Resposta longa:
Quais ponteiros são operandos válidos para expressões de exclusão de objeto único (por exemplo,
delete ptr;
) são abordados no padrão porexpr.delete
:Então, se você quer
delete ptr;
ser definido, precisa ser:T* ptr = new T;
)(ou no caso
T
de ser do tipo classe, então um ponteiro para uma dasT
classes base de também é permitido)Observe que isso descarta imediatamente qualquer trapaça como chamar
operator new
manualmente e depois usar placement-new para criar um objeto nessa memória.Existem algumas alternativas que você pode considerar:
1. Use um alocador
Os alocadores dividem a alocação e a construção em etapas separadas (e destruição e desalocação).
Então, supondo que você possa alterar seu código para usar
std::allocator
(ou qualquer outro alocador que satisfaça os requisitos de completude do alocador ), seria possível alocarT
's sem inicializá-los:ou dado que você marcou sua pergunta com unique-ptr :
Observe que você deve garantir que sempre crie um novo
T
objeto nesse armazenamento, caso contrário, ostd_allocator_deleter
deleter terá um comportamento indefinido.2. Não use uma expressão de exclusão
Uma solução simples seria simplesmente não usar uma expressão de exclusão.
Se você chamar explicitamente o destrutor seguido por uma chamada para
operator delete
seu programa também seria bem comportado:3. Crie um objeto com uma nova expressão e substitua-o
Se seus
T
tipos forem construtíveis por padrão de forma barata (ou você puder modificá-los para que sejam), você pode simplesmente usar uma new-expression normal e chamar seu destrutor imediatamente:Observe que você deve sempre colocar um novo
T
objeto no ponteiro retornado porallocate_uninitialized()
antes de passá-lo para uma expressão de exclusão, caso contrário, o comportamento será indefinido.