Розглянемо наступні три struct
s:
class blub {
int i;
char c;
blub(const blub&) {}
};
class blob {
char s;
blob(const blob&) {}
};
struct bla {
blub b0;
blob b1;
};
На типових платформах, де int
розміщено 4 байти, розміри, вирівнювання та загальна заливка 1 є такими:
struct size alignment padding
-------- ------ ----------- ---------
blub 8 4 3
blob 1 1 0
bla 12 4 6
Між сховищами blub
та blob
елементами немає перекриття , хоча розмір 1 blob
в принципі міг би "поміститися" в набивання blub
.
C ++ 20 вводить no_unique_address
атрибут, який дозволяє сусіднім порожнім членам ділити ту саму адресу. Це також явно дозволяє описаний вище сценарій використання прокладки одного члена для зберігання іншого. З cppreference (моє наголос):
Вказує, що цей член даних не повинен мати адресу, відмінну від усіх інших нестатичних членів даних свого класу. Це означає, що якщо член має порожній тип (наприклад, Allocator без стану), компілятор може оптимізувати його, щоб він не займав місця, як, якби це була порожня база. Якщо член не порожній, будь-які хвостові накладки в ньому також можуть бути повторно використані для зберігання інших членів даних.
Дійсно, якщо ми будемо використовувати цей атрибут на blub b0
, розмір bla
крапель до 8
, тож blob
справді зберігається в тому blub
, що видно на Godbolt .
Нарешті, ми переходимо до мого питання:
Який текст у стандартах (C ++ 11 до C ++ 20) перешкоджає цьому накладенню без no_unique_address
об’єктів, які не можна тривіально скопіювати?
Мені потрібно виключити тривіально копіювані (ТС) об’єкти із вищезазначеного, тому що для об'єктів ТС дозволено переходити std::memcpy
від одного об’єкта до іншого, включаючи суб’єкти членів, і якщо сховище буде перекрито, це порушиться (тому що все або частина сховища для сусіднього члена буде переписано) 2 .
1 Ми обчислюємо прокладку просто як різницю між розмірами конструкції та розміром усіх її складових, рекурсивно.
2 Тому у мене визначені конструктори копій: робити, blub
а blob
не тривіально копіювати .