Это хорошо определено / законно для размещения-новое несколько раз по одному и тому же адресу?

(Примечание: этот вопрос был мотивирован попыткой придумать хакерство препроцессора для создания выделения no-op, чтобы ответить на этот другой вопрос:

макрос, который принимает новый объект

...так что имейте это в виду!)

вот надуманный класс:

class foo {
private:
    int bar;
public:
    foo(int bar) : bar (bar)
        { std::cout << "construct foo #" << bar << std::endl; }
    ~foo()
        { std::cout << "destruct foo #" << bar << std::endl; }
};

...который я выделю следующим образом:

// Note: for alignment, don't use char* buffer with new char[sizeof(foo)] !
void* buffer = operator new(sizeof(foo));

foo* p1 = new (buffer) foo(1);
foo* p2 = new (buffer) foo(2);

/* p1->~foo(); */ /* not necessary per spec and problematic in gen. case */
p2->~foo();

на gcc, который у меня есть, я получаю " ожидаемый" результат:

construct foo #1
construct foo #2
destruct foo #2

что здорово, но может ли компилятор/среда выполнения отклонить это как злоупотребление и все еще быть на правой стороне спецификации?

как насчет многопоточности? Если мы на самом деле не заботимся о содержимом этого класса (скажем, это просто фиктивный объект в любом случае), он, по крайней мере, не рухнет, например, в еще более простом приложении, которое мотивировало это с помощью POD int?

3 ответов


Peforming размещение-new несколько раз на одном блоке памяти отлично. Более того, как бы странно это ни звучало, вам даже не требуется уничтожать объект, который уже находится в этой памяти (если таковой имеется). Стандарт явно разрешает это в 3.8 / 4

4 программа может завершить срок службы любого объекта, повторно используя хранилище который объект занимает или явно вызывая деструктор для объект тип класса с нетривиальным деструктором. Для объекта типа класса с нетривиальным деструктором, программа не требуется вызывать деструктор явно до хранения объект занимает повторно используется или освобождается;[...]

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

однако вызов деструктора на том же объекте дважды, что и в вашем коде, является не позволять. Как только вы создали второй объект в той же области памяти, вы фактически закончили жизнь первого объекта (хотя вы никогда не вызывали его деструктор). Теперь вам нужно только уничтожить второй объект.


foo* p1 = new (buffer) foo(1);
foo* p2 = new (buffer) foo(2);
p1->~foo();
p2->~foo();

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

тогда есть тот факт, что ваш буфер может быть неправильно выровнен для размещения объекта типа foo, который снова не является стандартным C++ (согласно C++03, Я думаю, что C++11 расслабляет это).

обновление: Относительно вопроса, указанного в название,

это хорошо определено / законно для размещения-новое несколько раз по одному и тому же адресу?

Да, он хорошо определен для размещения-new несколько раз по одному адресу, при условии, что он указывает на необработанную память.


нет - это не так.

при использовании placement new объект будет построен at адрес, который вы проходите. В этом примере вы передаете один и тот же адрес (т. е. &buffer[0]) дважды, поэтому второй объект просто уничтожает первый объект, который уже был создан в этом месте.

EDIT: я не думаю, что понимаю, что вы пытаетесь сделать.

Если у вас есть общий тип объекта (который может иметь нетривиальный ctor/dtor, которые могут выделять / освобождать ресурсы), и вы уничтожаете первый объект путем размещения new'ing поверх него без первого явного вызова его деструктора, это, по крайней мере, будет утечка памяти.