2015-03-18 1 views
3

Недавно я увидел фрагмент кода, который использовал буферы хранения для создания объектов, а затем просто поменял местами буферы, чтобы избежать накладных расходов на копирование. Вот простой пример использования целых числа:Обмен буферами хранения, содержащих размещение новых созданных объектов

std::aligned_storage_t<sizeof(int), alignof(int)> storage1; 
std::aligned_storage_t<sizeof(int), alignof(int)> storage2; 

new (&storage1) int(1); 
new (&storage2) int(2); 

std::swap(storage1, storage2); 

int i1 = reinterpret_cast<int&>(storage1); 
int i2 = reinterpret_cast<int&>(storage2); 

//this prints 2 1 
std::cout << i1 << " " << i2 << std::endl; 

Это чувствует, как непредсказуемое поведение в общем случае (в частности, замены буферов, а затем доступ к объектам, как если бы они были все еще там), но я не уверен, что говорит стандарт о таком использовании хранения и размещения новых. Любая обратная связь очень ценится!

+0

Должно быть хорошо для типов [CopyConstructible] (http://en.cppreference.com/w/cpp/concept/CopyConstructible). –

+0

Почему бы не swap 'reinterpret_cast (storage1)' и 'reinterpret_cast (storage2)'? – immibis

+0

@immibis, потому что это то, что делается в оригинальной части кода. – Veritas

ответ

1

Я подозреваю, что есть несколько факторов, оказывающих это не определено, но нам нужно только одно:

[C++11: 3.8/1]:[..]Время жизни объекта типа T заканчивается, когда:

  • если T - тип класса с нетривиальным деструктором (12.4), начинается вызов деструктора или
  • Место хранения, которое занимает объект, повторно используется или выпущено.

Все последующее использование использовать после того, как в конце срока службы, что плохо и неправильно.

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

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


Следующие действия могут быть в состоянии спасти вас:

[C++11: 3.8/7]: Если после жизни объекта закончилась и перед хранением которой объект занят повторно используется или отпущенной, новый объект создается в месте хранения, в котором был загружен исходный объект, указатель, указывающий на исходный объект, ссылка, относящаяся к исходному объекту, или имя исходного объекта будут автоматически ссылаться на новый объект и, если время жизни нового объекта, можно использовать для управления новым объектом [..]

& hellip; кроме того, что вы не создаете новый объект.

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

[C++11: 3.8/8]: Если программа заканчивается срок службы объекта типа T со статическим (3.7.1), поток (3.7.2) или автоматическое (3.7.3) время хранения, и если T имеет нетривиальный деструктор, программа должна гарантировать, что объект исходного типа занимает то же место хранения, когда подразумевается происходит вызов деструктора; в противном случае поведение программы не определено.

+2

Я не уверен, что первой цитаты достаточно, чтобы отклонить это как неопределенное поведение. В частности, мы не используем исходные объекты после свопинга, но побитовые копии, поэтому это должно быть хорошо для тривиально-копируемых типов, но не определено в общем случае, но это, вероятно, зависит от реализации std :: swap. Если он делает байтовую замену вместо создания другого контейнера и затем заменяет его, это всегда должно приводить к неопределенному поведению, так как срок жизни объектов заканчивается до того, как будет сделана копия. – Veritas

+0

@AlexandrosLiarokapis: Справедливые точки. Я не верю, что есть какая-либо формулировка, чтобы описать, как жизнь объекта может начинаться с того, что он побито скопирован откуда-то еще, хотя, откровенно говоря, это довольно неоднозначно. Возможно, единственный верный ответ - «никто не знает, избегайте»! –