2014-09-06 3 views
1

Я пытался понять концепцию memory reusing в C++. Представьте себе, у нас есть объект с нетривиальным деструктором:Концепция повторного использования памяти в C++

struct A 
{ 
    ~A(){ cout << "~A(): << endl; } 
}; 

struct B : A { }; 
A *a = new A; //Lifetime of a is starting 
A *b = new (a) B; //Lifetime of a has ended, lifetime of b is starting 

Раздел 3,8/7 говорят:

Если после жизни объекта закончилась и перед хранением которого объект занимаемым является повторно используемый или выпущенный, новый объект - , созданный в месте хранения, в котором был загружен исходный объект, указатель , указывающий на исходный объект, ссылка, которая ссылалась на исходный объект , или имя исходного объекта автоматически будет обратитесь к новому объект и после того, как срок службы нового объекта началось, может быть использован для управления новый объект, если:

[...]

То есть, потому что срок службы a не было было закончено в то время, когда мы позвонили для повторного использования памяти, расположенной на a, мы не можем применить это правило к этому примеру. Итак, что именно правило описывает поведение в моем случае?

+0

Есть ли A * b = новый (a) B; 'даже компилировать? –

+0

Он должен скомпилировать, так как 'B' происходит от' A'. –

+0

Компилятору не требуется реализовать пустую оптимизацию базового класса для удаления дополнительного пространства «A» с «B». Таким образом, вы не можете гарантировать, что 'sizeof (A)> = sizeof (B)', даже если он разрешен, он не переносится. –

ответ

2

Применимое правило для этого изложено в §3.8 [базовое.жизнь]/р1 и 4:

Время жизни объекта типа T заканчивается, когда:

  • если Т тип класса с нетривиальным деструктора (12.4), запускается деструктор вызов , или
  • Хранилище, которое объект занимает, повторно используется или освобождается.

4 Программы может закончиться сроком службы любого объекта путем повторного хранение которой объект занимает или путем явного вызова деструктора для объекта типа класса с нетривиальным деструктором. Для объекта типа класса с нетривиальным деструктором программа не , требуемая для вызова деструктора явно перед хранилищем, которое объект занимает повторно или освобожден; однако, если нет явного вызова деструктора или если delete-expression (5.3.5) - , который не используется для освобождения хранилища, деструктор не должен быть неявным образом и любая программа, которая зависит от стороны эффекты , созданные деструктором, имеют неопределенное поведение.

Так A *b = new (a) B; повторно на хранение A объекта, созданного в предыдущем заявлении, которое вполне определенное поведение при условии, что sizeof(A) >= sizeof(B)*. То, что время жизни объекта A закончилось в силу того, что его хранилище повторно используется. Деструктор A не вызывается для этого объекта, и если ваша программа зависит от побочного эффекта, создаваемого этим деструктором, у него есть неопределенное поведение.

Приведенный вами параграф, §3.8 [basic.life]/p7, регулирует, когда указатель/ссылка на исходный объект может быть повторно использован. Поскольку этот код не удовлетворяет критериям, перечисленным в этом пункте, вы можете использовать только a только в ограниченных путях, разрешенных §.3.8 [basic.life]/p5-6, или неопределенных результатах поведения (пример и сноска опущены):

5 Перед началом жизни объекта началась, но после хранения которого объект будет занимать был выделен или после жизни объекта закончился и перед хранением которой объект занят повторно используется или выпущенный, может использоваться любой указатель, который ссылается на хранилище , где может находиться или находился объект, но только в ограниченных количествах. По строительству или уничтожению объекта см. 12.7. В противном случае такой указатель относится к выделенному хранилищу (3.7.4.2) и с использованием указателя, как если бы указатель имел тип void*, имеет значение . Такой указатель может быть разыменован, но полученное значение может использоваться только ограниченным образом, как описано ниже.Программа имеет неопределенное поведение, если:

  • объект будет или была типа класса с нетривиальным деструктором и указатель используется в качестве операнда удалить-выражение,
  • указатель используется для доступа к нестатическому элементу данных или вызова нестатической функции-члена объекта, или
  • указатель неявно преобразован (4.10) в указатель на тип базового класса или
  • указатель используется как операнд static_cast (5.2.9) (за исключением того, когда преобразование является void* или void* и впоследствии char* или unsigned char*), или
  • указатель используется в качестве операнда dynamic_cast (5.2.7).

6 Аналогичным образом, до того, как срок службы объекта началось, но после того, как хранения которой объект будет занимать было выделено или, после того, как время жизни объекта закончилась и перед хранением которой объект занимал повторно используется или освобождается, любое значение gl, которое ссылается на исходный объект , может использоваться, но только ограниченным образом. Для объекта под строительство или уничтожение, см. 12.7. В противном случае такой glvalue относится к выделенному хранилищу (3.7.4.2), и использование свойств значения , которое не зависит от его значения, является корректным. Программа имеет неопределенное поведение, если:

  • именующее к Rvalue преобразования (4.1) применяется к такому glvalue,
  • glvalue используется для доступа к нестатический элемент данных или вызов не -static функция член объекта или
  • glvalue неявно преобразуется (4.10) в отношении типа базового класса, или
  • glvalue используется в качестве операнда static_cast (5.2.9), за исключением, когда конверсия в конечном счете равна cv char& или cv unsigned char&, или
  • glvalue используется как операнд dynamic_cast (5.2.7) или как операнд typeid.

* Для предотвращения UB от случаев, когда sizeof(B) > sizeof(A), мы можем переписать A *a = new A; в char c[sizeof(A) + sizeof(B)]; A* a = new (c) A;.

+0

U означает, что если поведение не указано в стандарте, оно не определено, правильно? – user3663882

+0

Почему 'char c [sizeof (A) + sizeof (B)]; A * a = new (c) A; 'не вызывает UB? Правила из 3.8 описывают поведение, когда ** время жизни закончилось и до того, как хранилище повторно используется ** или выделена память, а время жизни не начинается. 'A * a = new (c) A;' используется, когда время жизни не закончилось. То есть все эти правила неприменимы. Думаю, этот параграф действительно смущен. – user3663882

1

Есть некоторые потенциальные проблемы с этим:

  1. Если B больше, чем А, он будет перезаписывать байтов не выделенные - что неопределенное поведение.
  2. Деструктор А не вызывается для a (или b - ваш код не показывает, delete a или delete b или ни один из них). Это очень важно, если либо для A или B деструктора делает что-то вроде подсчета ссылок, замков, открепления памяти (в том числе std:: контейнеров, такие как std::vector или std::string) и т.д.

Если a снова после создания не используются b, вам все равно нужно вызвать деструктор A, чтобы убедиться, что срок его службы закончился - см. Пример в третьем, после того, как вы указали раздел. Поэтому, если ваша цель состояла в том, чтобы избежать «дорогого» вызова деструктора, тогда ваш код не соблюдает правила, приведенные в разделе 3.8/7 стандарта.

Вы также нарушение пули:

  • Исходный объект был наиболее производный объект (1.8) типа Т и нового объекта является наиболее производный объект типа T.

как A не является самым производным типом.

Таким образом, «сломан». Даже в тех случаях, когда он работает (например, меняется на A* a = new B;), его следует обескуражить, поскольку это может привести к тонким и сложным ошибкам.

+0

Первый срок жизни объекта 'A' ​​закончился, как только его хранилище повторно используется. Не требуется деструктор. –

0

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

Примечание: Расположенная память имеет размер B для размещения потенциального размера между A и B.

Примечание 2: с вашей реализацией класса A это не сработает. ~A() необходимо сделать виртуальным !!

A *b = new B; //Lifetime of b is starting. It is important that we use `new B` rather than `new A` so as to get the correct size. 
b->~B(); //lifetime of b has ended. The memory still remain allocated however. 
A *a = new (a) A; //lifetime of a is starting 
a->~A(); // lifetime of a has ended 

// a is still allocated but in an undefined state 

::operator delete(b); // release the memory allocated without calling the destructor. This is different from calling 'delete b' 

Я считаю, что вызовoperator delete на базовый указатель должен быть безопасным. Пожалуйста, поправьте меня, если это не так.

В качестве альтернативы, если выделить память для как char буфера, вы можете использовать размещение нового построить A и B объектов и безопасно вызывать delete[] для освобождения буфера (с char имеет тривиальный деструктор):

char* buf = new char[sizeof(B)]; 
A *a = new (a) A; 
a->~(); 
A *b = new (a) B; 
b->~B(); 
delete[] buf; 
Смежные вопросы