Почему определение POD "стандартной компоновки" в C++11 таково?
Я смотрю на новое, смягченное определение POD в C++ 11 (раздел 9.7)
Класс стандартного макета - это класс, который:
- не имеет нестатических членов-данных типа нестандартного класса макета (или массива таких типов) или ссылки,
- не имеет виртуальных функций (10.3) и виртуальных базовых классов (10.1),
- имеет одинаковый контроль доступа (пункт 11) для всех нестатических элементов данных,
- не имеет базовых классов нестандартной компоновки,
- либо не имеет нестатических членов данных в самом производном классе и не более одного базового класса с нестатическими членами данных, или не имеет базовых классов с нестатическими членами данных, и
- не имеет базовых классов того же типа, что и первый нестатический элемент данных.
Я выделил биты, которые меня удивили.
Что бы пошло не так, если бы мы допускали элементы данных с различным контролем доступа?
Что бы пошло не так, если бы первый элемент данных был также базовым классом? т.е.
struct Foo {};
struct Good : Foo {int x; Foo y;};
struct Bad : Foo {Foo y; int x;};
Я признаю, что это странная конструкция, но почему Bad
быть запрещено, но не Good
?
Наконец, что может пойти не так, если более чем в одном составляющем классе есть члены-данные?
6 ответов
Вам разрешено приводить стандартный адрес объекта класса макета к указателю на его первый член и обратно одним из более поздних абзацев, что также часто делается в C:
struct A { int x; };
A a;
// "px" is guaranteed to point to a.x
int *px = (int*) &a;
// guaranteed to point to a
A *pa = (A*)px;
Чтобы это работало, первый член и полный объект должны иметь один и тот же адрес (компилятор не может корректировать указатель int никакими байтами, потому что он не может знать, является ли он членом A
или нет).
Наконец, что может пойти не так, если более чем в одном составляющем классе есть члены-данные?
Внутри класса члены распределяются по возрастающим адресам в соответствии с порядком объявления. Однако C++ не определяет порядок распределения элементов данных по классам. Если и у производного класса, и у базового класса есть элементы данных, Стандарт не определяет порядок их адресов специально, чтобы обеспечить полную гибкость реализации в макетировании памяти. Но для того, чтобы приведенный выше актерский состав сработал, вам нужно знать, что является "первым" членом в порядке распределения!
Что бы пошло не так, если бы первый элемент данных был также базовым классом?
Если базовый класс имеет тот же тип, что и первый член данных, реализации, которые помещают базовые классы перед объектами производного класса в памяти, должны иметь байт заполнения перед членами данных производного класса в памяти (базовый класс будет иметь размер один).), чтобы избежать использования одного и того же адреса для базового класса и первого члена данных (в C++ два разных объекта одного типа всегда имеют разные адреса). Но это снова сделало бы невозможным приведение адреса объекта производного класса к типу его первого члена данных.
Это в основном о совместимости с C++03 и C:
- тот же контроль доступа - в реализациях C++03 разрешено использовать спецификаторы контроля доступа как возможность переупорядочивать (группы) членов класса, например, чтобы упаковать его лучше.
- более одного класса в иерархии с нестатическими членами данных - C++03 не говорит, где расположены базовые классы, или нет ли отступы в подобъектах базового класса, которые будут присутствовать в законченном объекте того же типа.
- базовый класс и первый член того же типа - из-за второго правила, если тип базового класса используется для члена данных, то это должен быть пустой класс. Многие компиляторы реализуют пустую оптимизацию базового класса, поэтому то, что Андреас говорит о подобъектах с одинаковым адресом, было бы правдой. Я не уверен, хотя, что это о классах стандартной компоновки, это означает, что для подобъекта базового класса плохо иметь тот же адрес, что и у первого члена данных того же типа, но не имеет значения, когда подобъект базового класса имеет тот же адрес, что и у первого члена данных другого типа. [Правка: это потому, что разные объекты одного типа имеют разные адреса, даже если они являются пустыми подобъектами. Благодаря Йоханнесу]
C++ 0x, вероятно, мог бы определить, что эти вещи также являются типами стандартной компоновки, и в этом случае он также определил бы, как они планируются, в той же степени, что и для типов стандартной компоновки. Ответ Йоханнеса идет дальше, посмотрите на его пример замечательного свойства классов стандартной компоновки, которым мешают эти вещи.
Но если бы это было сделано, то некоторые реализации были бы вынуждены изменить то, как они размещают классы, чтобы соответствовать новым требованиям, что является помехой для структурной совместимости между различными версиями этого компилятора до и после C++0x. В основном, это нарушает C++ ABI.
Мое понимание того, как была определена стандартная структура, состоит в том, что они смотрели на то, какие требования POD можно было ослабить, не нарушая существующие реализации. Поэтому я предполагаю без проверки, что приведенные выше примеры являются примерами, когда некоторые существующие реализации C++03 действительно используют не-POD-природу класса, чтобы сделать что-то, что несовместимо со стандартной компоновкой.
Что бы пошло не так, если бы мы допускали элементы данных с различным контролем доступа?
Текущий язык говорит, что компилятор не может переупорядочивать элементы под одним и тем же контролем доступа. Подобно:
struct x
{
public:
int x;
int y;
private:
int z;
};
Здесь x должен быть расположен перед y, но нет ограничений на z относительно x и y.
struct y
{
public:
int x;
public:
int y;
};
Новая формулировка гласит, что y
все еще POD, несмотря на два public
s. Это на самом деле ослабление правил.
Что касается почему Bad
не разрешено, дайте мне цитату из статьи, которую я нашел:
Это гарантирует, что два подобъекта, которые имеют один и тот же тип класса и принадлежат одному и тому же самому производному объекту, не будут размещены по одному и тому же адресу.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2172.html
Из п. 5 кажется, что оба они не являются модулями, так как наиболее производный класс имеет не статический член данных (int), он не может иметь базовый класс с нестатическим членом данных.
Я понимаю это как: "только один из" базового "класса (т.е. сам класс или один из классов, от которых он наследуется) может иметь нестатические члены-данные"
struct Good
также не является стандартным макетом, так как Foo и Good имеют нестатический элемент данных.
Таким образом, хорошо должно быть:
struct Foo {int foo;};
struct Good : public Foo {Foo y;};
который не удовлетворяет 6-й пули. Отсюда 6-я пуля?