Отказ от ответственности: Это нюансированный вопрос C ++, тесно связанный со строгим чтением спецификации C ++.
В C ++ традиционный способ использования конструкции на месте:
void * pStorage = myPooledAllocationFunction ( /*pool number*/1 ) ; // allocate from pool number 1, for example
MyClass * p = new (pStorage) MyClass () ;
Однако важно отметить, что reinterpret_cast<void*> ( p )
не обязательно равен pStorage
,
Примечание: я был в замешательстве, когда только что прочитал, что новое место размещения может сделать гораздо больше, так как на самом деле просто говорит: «Пожалуйста, позвоните operator new
с этими дополнительными аргументами »и традиционным способом его использования просто имеет реализацию по умолчанию в C ++.
Я считаю, что традиционный метод освобождения этого вновь созданного объекта заключается в следующем:
p->~MyClass() ;
myPoolDeallocationFunction ( /*pool number*/1 ) ; // deallocate pool 1 in its entirety
Что предполагает, что вы в состоянии сделать это. Но что, если вы не? Что если вам нужно вернуть исходный указатель из p
?
Это действительно?
p->~MyClass() ;
myPooledDeallocationFunction ( static_cast<void*> ( p ) ) ;
Спецификация C ++ (C ++ 2003: 5.2.9.10) указывает, что это действительно для использования static_cast
конвертировать из MyClass*
в void*
а затем вернуться к MyClass*
,
Есть еще одно упоминание (C ++ 2003: 3.8.5) в связи с этим, указав, что вы можете, после вызова деструктора, но до освобождения памяти, выполнить очень специфический набор вещей с этим указателем. Для наших целей все, что нас волнует, это то, что оно включает void*
(который действителен, потому что старый указатель «указывает на действительную память»).
Это не говорит, что если void*
был оригинал void*
используется для строительства на месте.
Так: как получить исходный указатель, используемый для создания на месте из результирующего указателя?
Ты можешь использовать dynamic_cast<void*>
извлечь исходный указатель из базового класса. Полученный указатель будет всегда указать на самый производный класс, a.k.a. весь объект.
Насколько я знаю, это незаконно для pStorage
а также p
быть разными адресами.
Похоже, это академический вопрос, который требует педагогического ответа.
Но если вы хотите развлечь грубую силу и невежество, это может помочь вам вырезать блоки пула из необработанной памяти даже на границе двух степеней, в результате чего блоки гарантированно будут больше любого объекта. быть размещенным (и предположительно правильно выровненным).
(Затем замаскируйте соответствующие младшие биты p, чтобы получить оригинальную новую цель.)
Большинство компиляторов используют действительно простую реализацию размещения new, как указано в 11.2.4 в «Язык программирования C ++».
Например VS — 2013:
#ifndef __PLACEMENT_NEW_INLINE
#define __PLACEMENT_NEW_INLINE
inline void *__CRTDECL operator new(size_t, void *_Where) _THROW0()
{ // construct array with placement at _Where
return (_Where);
}
Но в книге нет эксплицитно объяснил, должен ли возврат нового размещения обязательно совпадать с переданным указателем.
Итак, AFAIK, это должно работать. И dynamic_cast<void*>
По указанию @Puppy для получения оригинального указателя выглядит отличная идея.
Но я хотел бы поговорить о явном вызове деструктора и о том, что говорит об этом Страуструп:
«Обратите внимание, что явных вызовов деструкторов следует избегать, кроме как в реализации ресурса
классы управления. «
Я предполагаю, что, поскольку у вас нет исходного указателя хранилища в момент уничтожения, это потому, что он не выполняется в классе управления ресурсами.
Обычно я думаю, что нахождение подобных сбоев или вопросов в программировании на C ++ связано с некоторыми недостатками дизайна (конечно, я не ставлю под сомнение дизайн того, что вы делали), но, возможно, их следует использовать в качестве сигналов, чтобы думать о том, что именно мы делаем. делают.
В этом конкретном примере смешивание объектов, размещенных с размещением новых и обычных объектов без явного класса управления, для объектов, размещенных с размещением новых, определенно не очень хорошая идея.