Глобальное "размещение" удалить [] - PullRequest
9 голосов
/ 27 ноября 2009

Я пытаюсь заменить новый / удалить своим собственным распределителем (ями). Таким образом, переопределение размещения новых и удаления - вполне доволен этим. Выглядит примерно так ...

void* operator new( size_t size, Allocator* a )
{
    return a->Alloc( size );
}

template<class T> inline void MyDelete( T* p, Allocator* a )
{
    if( p )
    {
        p->~T();
        a->Free( p );
    }
}

Язык C ++ указывает, что для удаления размещения вы должны явно вызвать ~ dtor. Компилятор не делает это за вас. Является ли это шаблонным оператором удаления или явной функцией, как показано.

См. http://www2.research.att.com/~bs/bs_faq2.html#placement-delete

Проблема в том, как заставить это работать для удаления массива []? Я знаю, что мне нужно перебрать массив и сам вызвать ~ dtor. Поэтому мне нужен размер массива,

Отредактировано для ясности

Я могу сохранить эту информацию или вывести ее из размера блока. Однако проблема в том, что компилятор (MSVC v9) делает разные вещи, если я выделяю массив объектов с деструкторами по сравнению с объектами без, т. Е. Если есть dtor, он выделяет дополнительные 4 байта. Это потому, что компилятор для стандартного delete [] должен сделать то же самое и может создать соответствующий код для delete [].

Однако в моем собственном "размещения" delete [] у меня нет никакого способа узнать, что сделал компилятор или определить безопасно во время компиляции, если класс имеет dtor.

1019 * Е.Г. *

char buf[ 1000 ];

MyClass* pA = new( buf ) MyClass[ 5 ];

Здесь значение pA равно buf + 4, если существует ~ MyClass (), а объем выделяемой памяти равен sizeof (MyClass) * 5 + 4. Однако если dtor не существует, то pA == buf и количество выделенная память - sizeof (MyClass) * 5.

Итак, мой вопрос - это поведение языка является стандартным и согласованным во всех компиляторах или оно свойственно MSVC? У кого-нибудь еще есть хорошее решение этой проблемы? Я предполагаю, что единственный вариант - не использовать new [] и делать конструкцию самостоятельно, что нормально, но тогда синтаксис вызывающего кода немного необычен ... или заставляет каждый класс иметь деструктор.

Ответы [ 4 ]

4 голосов
/ 27 ноября 2009

Если есть сомнения, обратитесь к эксперту:

http://www.stroustrup.com/bs_faq2.html#placement-delete

Но как мы можем позже правильно удалить эти объекты? Причина, по которой нет встроенного «удаления размещения» для соответствия новому размещению, заключается в том, что нет общего способа гарантировать, что он будет использован правильно. Ничто в системе типов C ++ не позволяет нам сделать вывод, что p1 указывает на объект, выделенный в Arena a1. Указатель на любой X, размещенный в любом месте, может быть назначен p1.

Остальная часть ссылки описывает, как исправить ситуацию.

3 голосов
/ 28 ноября 2009

Краткий ответ:

Нет прямой поддержки для этого использования. Если вы перегружаете новый с другой подписью, компилятор считает это перегрузкой нового (не размещения нового) и добавляет свой собственный код учета. Нет никакого способа (я могу найти) сказать компилятору «размотать вашу бухгалтерию и вызвать мою перегрузку удаления, соответствующую этой подписи» - он будет вставлять только код для размотки бухгалтерии при вызове void operator delete(void* p) или void operator delete[](void* p).

Если вы переопределяете новое новой подписью, компилятору нравится, когда вы определяете удаление с соответствующей подписью в случае исключений во время новой - это единственный раз, когда он используется.

Нет удаления размещения в том смысле, что оно не вызывается, но оно определено в случае исключений (ничего не делать).

Длинный ответ:

Эта тема поднимает некоторые интересные моменты:

  1. Что именно void* operator new[](size_t sz, Allocator* a) перегружает?
  2. Есть или нет "удаление места размещения".
  3. Как можно вызвать void operator delete[](void* p, Allocator* a) таким образом, как компилятор вставляет свою завершающую работу по бухгалтерскому учету?

Пункт 1: Много разговоров о перегрузке размещения новых. Учитывая, что компилятор вставляет код бухгалтерского учета, он должен придерживаться мнения, что void* operator new[](size_t sz, Allocator* a) объявляет перегрузку (не размещение) новой. Он никогда не вставит бухгалтерский код для размещения новых, потому что смысл размещения новых заключается в том, что вы сами с этим справляетесь.

Пункт 2: Р.Е. "нет такой вещи, как удаление размещения", вы найдете что-то, что ужасно похоже на это (и прокомментировано как таковое), например, VS2k8 новый заголовок. Это просто заглушка, используемая в тех случаях, когда во время размещения возникает исключение. Тем не менее, представляется верным, что вы не можете вызвать удаление размещения значимым образом.

Пункт 3: Если есть способ, я не могу его найти. Это суть проблемы.

С точки зрения практического решения проблемы, это похоже на провал.

например:

//intention: user provides memory pool, compiler works out how many bytes required
//and does its own book-keeping, as it would for a void* operator new[](size_t sz) overload
//calling syntax: CObj* pMyArr = new(pMyMemPool) CObj[20];
void* operator new[](size_t sz, IAlloc* pMemPool)
{ return pMemPool->alloc(sz); }

//problem: don't know the syntax to call this! 
//e.g. delete[](pMyMemPool) pMyArr is syntax error
void* operator delete[](void* p, IAlloc* pMemPool)
{ return pMemPool->free(p); }

//nb: can be called as operator delete(pMyArr, pMyMemPool);
//but compiler does not finish its book-keeping or call dtors for you in that case.

Обратите внимание, что эта асимметрия существует и для не-массива new и delete. Однако, поскольку (эмпирически) рассматриваемый компилятор не ведет никакой дополнительной бухгалтерии, его можно заставить работать. Опять же, если это закреплено в стандарте, я не знаю.

    void* operator new(size_t sz, IAlloc* pMemPool)
    { return pMemPool->alloc(sz); }


//don't know syntax to get this called by compiler!
    void operator delete(void* p, IAlloc* pMemPool)
    { pMemPool->free(p); }

    //is ok though, can work around
    template<class T> void tdelete(void* p, IAlloc* pMemPool)
    {
     //no problems, p points straight at object
     p->~T();

     operator delete(p, pMemPool);
     //OR just
     pMemPool->free(p);
    }

    void* operator new[](size_t sz, IAlloc* pMemPool)
    { return pMemPool->alloc(sz); }

    //again, don't know syntax to end up here.
    void operator delete[](void* p, IAlloc* pMemPool)
    { pMemPool->free(p); }

    //can't work around this time!
    template<class T> void tarrdelete(void* p, IAlloc* pMemPool)
    {
     //problem 1: how many to dtor?
     for(int i=0; i<???; ++i)
     { reinterpret_cast<T*>(p+i)->~T(); }
     //problem 2: p points at first element in array. this is not always the address
     //that was allocated originally.
     pMemPool->free(?);

     //as already explained by OP, no way to tell if p is address allocated or
     //address allocated+4 bytes, or something else altogether. this means no way to know what address to un-alloc or how many dtors to call. 

    }

Наконец, я сообщу об этом. - перегрузки без расширенного списка параметров работают:

//sz may include extra for book-keeping
void* operator new[](size_t sz)
{ return GAlloc->alloc(sz); }

//works fine, compiler handled book-keeping and p is the pointer you allocated
void operator delete[](void* p)
{ return GAlloc->free(p); }

Сводка : существует ли синтаксис, позволяющий вызывать перегрузку удаления с расширенным списком параметров, с включенной «магией» компилятора. Или есть способ добавить параметры для размещения новых путем переопределения?

Подозреваемый ответ : Нет.

Следствие : Вы не можете отклониться от 6 встроенных новых подписей с полной свободой. Это приводит к перегрузке нового, при этом ведется бухгалтерский учет, сгенерированный компилятором, но нет доступа к соответствующему удалению для отмены бухгалтерского учета.

Предостережение : Вы можете отклониться от встроенных подписей, но только для того, чтобы ввести код, который вам не нужно снова обрабатывать при удалении (например, инструментарий). Если вы перейдете к версии void* operator new(size_t s) для выделения, удаление все равно будет работать как обычно.

(Некоторые констатации фактов взяты из экспериментов в отладчике и могут применяться только к MSVC8 (cl9). OP сидит рядом со мной.)

1 голос
/ 27 ноября 2009

Не существует такой терминологии, как «удаление размещения». Как вы сказали, если вы выделяете что-то с новым размещением, то, когда приходит время для освобождения, вам нужно вручную вызвать деструктор, а затем позаботиться о реальном буфере памяти, выделенном для размещения нового.

Но то, что вы пытаетесь сделать, невозможно без ручного отслеживания ваших собственных размеров распределения. Причина в том, что весь смысл «размещения нового» состоит в том, чтобы отделить выделение от инициализации объекта. Таким образом, с размещением new действие по выделению буфера памяти полностью отделено от конструирования или разрушения любых объектов, которые могут (или не могут) когда-либо жить в этом буфере.

Так, например, если вы выделяете некоторый буфер, например char buf[1000], и затем используете размещение new для создания массива Foo объектов в этом буфере, где C ++ должен хранить информацию о размере массива? Он не будет хранить его в вашем буфере, потому что он не знает, что вы хотите сделать с этим буфером. Таким образом, вы должны записать размер каждого выделения, а затем правильно связать его с освобождением.

0 голосов
/ 27 ноября 2009

Вы можете посмотреть указатель вверх в вашем распределителе, узнать размер из вашей бухгалтерии и вычислить количество элементов, используя sizeof.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...