Инициализация объединения с нетривиальным конструктором
У меня есть структура, которую я создаю пользовательский конструктор для инициализации членов в 0. В более старых компиляторах я видел, что в режиме выпуска без установки memset в 0 значения не инициализируются.
Теперь я хочу использовать эту структуру в объединении, но получаю ошибки, потому что она имеет нетривиальный конструктор.
Итак, вопрос 1. Гарантирует ли реализованный компилятором по умолчанию конструктор, что все члены структуры будут инициализированы нулем? Нетривиальный конструктор просто устанавливает memset всех членов в '0', чтобы обеспечить чистую структуру.
Вопрос 2: Если в базовой структуре должен быть указан конструктор, как может быть реализовано объединение, содержащее этот элемент и обеспечивающее 0 инициализированных базовых элементов?
7 ответов
Вопрос 1: Конструкторы по умолчанию инициализируют члены POD равными 0 в соответствии со стандартом C++. Смотрите цитируемый текст ниже.
Вопрос 2: Если конструктор должен быть указан в базовом классе, тогда этот класс не может быть частью объединения.
Наконец, вы можете предоставить конструктор для вашего объединения:
union U
{
A a;
B b;
U() { memset( this, 0, sizeof( U ) ); }
};
Для Q1:
Из C++03, 12.1 Конструкторы, стр 190
Неявно определенный конструктор по умолчанию выполняет набор инициализаций класса, который будет выполняться пользовательским конструктором по умолчанию для этого класса с пустым списком mem-initializer-list (12.6.2) и пустым телом функции.
Из C++03, 8.5 Инициализаторы, стр. 145
По умолчанию инициализировать объект типа T означает:
- если T является типом класса, отличным от POD (пункт 9), вызывается конструктор по умолчанию для T (и инициализация является некорректной, если у T нет доступного конструктора по умолчанию);
- если T является типом массива, каждый элемент инициализируется по умолчанию;
- в противном случае объект инициализируется нулями.
Инициализация нуля объекта типа T означает:
- если T - скалярный тип (3.9), объекту присваивается значение 0 (ноль), преобразованное в T;
- если T является типом класса, не являющимся объединением, каждый нестатический член данных и каждый подобъект базового класса инициализируются нулями;
- если T является типом объединения, первый именованный элемент данных объекта инициализируется нулями;
- если T является типом массива, каждый элемент инициализируется нулями;
- если T является ссылочным типом, инициализация не выполняется.
Для Q2:
Из C++03, 12.1 Конструкторы, стр 190
Конструктор тривиален, если он является неявно объявленным конструктором по умолчанию и если:
- его класс не имеет виртуальных функций (10.3) и виртуальных базовых классов (10.1), и
- все прямые базовые классы этого класса имеют тривиальные конструкторы, и
- для всех нестатических членов данных своего класса, которые имеют тип класса (или его массив), каждый такой класс имеет тривиальный конструктор
Из C++03, 9,5 Союзов, стр. 162
Объединение может иметь функции-члены (включая конструкторы и деструкторы), но не виртуальные (10.3) функции. У союза не должно быть базовых классов. Объединение не должно использоваться в качестве базового класса. Объект класса с нетривиальным конструктором (12.1), нетривиальным конструктором копирования (12.8), нетривиальным деструктором (12.4) или нетривиальным оператор присваивания копии (13.5.3, 12.8) не может быть членом объединения, а также не может содержать массив таких объектов
В C++11 все изменилось к лучшему.
Теперь вы можете легально сделать это, как описано самим Страуструпом (эту ссылку я нашел в статье в Википедии на C++11).
Пример в Википедии выглядит следующим образом:
#include <new> // Required for placement 'new'.
struct Point {
Point() {}
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U {
int z;
double w;
Point p; // Illegal in C++03; legal in C++11.
U() {new(&p) Point();} // Due to the Point member, a constructor
// definition is now *required*.
};
Страуструп углубляется в некоторые детали.
Члены профсоюза AFAIK не могут иметь конструкторов или деструкторов.
Вопрос 1: нет, такой гарантии нет. Любой POD-член, которого нет в списке инициализации конструктора, инициализируется по умолчанию, но это с указанным вами конструктором и имеет список инициализатора. Если вы не определяете конструктор или определяете конструктор без списка инициализатора и пустого тела, POD-члены не будут инициализированы.
Члены, не являющиеся POD, всегда будут создаваться через конструктор по умолчанию, который в случае синтеза снова не будет инициализировать POD-члены. Учитывая, что члены объединения могут не иметь конструкторов, вы в значительной степени будете уверены, что POD-члены структур в объединении не будут инициализированы.
Вопрос 2: вы всегда можете инициализировать структуры / объединения следующим образом:
struct foo
{
int a;
int b;
};
union bar
{
int a;
foo f;
};
bar b = { 0 };
Как упоминалось в комментарии Грега Роджерса к посту unwesen, вы можете дать своему объединению конструктор (и деструктор, если хотите):
struct foo
{
int a;
int b;
};
union bar
{
bar() { memset(this, 0, sizeof(*this)); }
int a;
foo f;
};
Вы можете сделать что-то подобное?
class Outer
{
public:
Outer()
{
memset(&inner_, 0, sizeof(inner_));
}
private:
union Inner
{
int qty_;
double price_;
} inner_;
};
... или что-то в этом роде?
union MyUnion
{
int qty_;
double price_;
};
void someFunction()
{
MyUnion u = {0};
}
Это интересный вопрос, и в других ответах есть много полезной информации. Кроме того, будет полезно узнать, каков эффект указания конструктора по умолчанию через
=default
синтаксис .
Для класса, который является членом объединения, такой конструктор по умолчанию «по умолчанию» предпочтительнее определяемого пользователем конструктора по умолчанию без списка инициализации и пустого тела. Примечание. В случае, если определяемый пользователем конструктор по умолчанию нетривиален, например вызовmemset
и т. д., тогда ответ Дэн-мана показывает, что нужно сделать (хотя, например, определяется, что конструктор по умолчанию не имеет списка инициализации и пустого тела).
Что касается вопроса 1, конструктор по умолчанию «по умолчанию» подчеркнет разницу между инициализацией по умолчанию и инициализацией значения.
Для класса под названиемC
, если конструктор по умолчанию явно определен пользователем какC() {}
(т. е. с пустым телом и без списка инициализации), то это приведет к инициализации по умолчанию, когда объект создается таким образом: . Однако, если конструктор по умолчанию указан какC()=default;
затемC c_obj{};
приводит к инициализации значенияc_obj
.
Что касается вопроса 2, ответ Дэнмана очень полезен. Это будет упрощено следующим образом с помощью конструктора по умолчанию:
#include <new> // Required for placement 'new'.
struct Point {
Point()=default; // not `Point() {};`
Point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union U {
int z;
double w;
Point p;
// No need to specify a default constructor.
// It is needed with `Point() {};` which is considered
// as a user defined default constructor.
};
int main() {
...
U u; // implicitly generated default constructor of U is called.
new(&u.p)Point(); // activate the Point member of U
// using placement new.
...
}
Вам нужно будет дождаться, пока компиляторы поддержат C++0x, чтобы получить это. До тех пор, извините.