Подтвердить что ты не робот

Анонимный союз С++ 11 с нетривиальными членами

Я обновляю мою структуру, и я хотел добавить к ней члена std::string. Исходная структура выглядит следующим образом:

struct Value {
  uint64_t lastUpdated;

  union {
    uint64_t ui;
    int64_t i;
    float f;
    bool b;
  };
};

Просто добавление члена std::string к объединению, конечно, вызывает ошибку компиляции, потому что обычно нужно добавлять нетривиальные конструкторы объекта. В случае std::string (текст из informit.com)

Так как std::string определяет все шесть специальных функций-членов, U будет иметь неявно удаленный конструктор по умолчанию, конструктор копирования, оператор присваивания копии, конструктор перемещения, оператор назначения перемещения и деструктор. Эффективно это означает, что вы не можете создавать экземпляры U, если вы явно не определяете некоторые или все специальные функции-члены.

Затем веб-сайт продолжает приводить следующий пример кода:

union U
{
int a;
int b;
string s;
U();
~U();
};

Однако я использую анонимный союз внутри структуры. Я спросил ## С++ на freenode, и они сказали мне, что правильный способ сделать это - поместить конструктор в структуру вместо этого и дать мне этот примерный код:

#include <new>

struct Point  {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {}
    int x_, y_;
};

struct Foo
{
  Foo() { new(&p) Point(); }
  union {
    int z;
    double w;
    Point p;
  };
};

int main(void)
{
}

Но оттуда я не могу понять, как сделать остальные специальные функции, определенные std::string, и, кроме того, я не совсем понимаю, как работает ctor в этом примере.

Могу ли я заставить кого-то объяснить это мне немного понятнее?

4b9b3361

Ответ 1

Здесь нет необходимости размещать новое место.

Члены Variant не будут инициализированы конструктором, сгенерированным компилятором, но не должно быть проблем с выбором и инициализацией, используя обычный список-инициализатор ctor. Члены, объявленные внутри анонимных объединений, фактически являются членами содержащего класса и могут быть инициализированы в конструкторе содержащего класса.

Это поведение описано в разделе 9.5. [class.union]:

Союзный класс является объединением или классом, который имеет анонимный союз как прямой член. Объединенный класс X имеет набор вариантов. Если X является объединением, его членами варианта являются нестатические члены данных; в противном случае его членами варианта являются нестатические члены данных всех анонимных объединений, которые являются членами X.

и в разделе 12.6.2 [class.base.init]:

Инициализатор ctor может инициализировать вариантный член класса конструкторов. Если ctor-initializer задает более одного mem-инициализатора для одного и того же элемента или для одного и того же базового класса, ctor-инициализатор плохо сформирован.

Таким образом, код может быть просто:

#include <new>

struct Point  {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {}
    int x_, y_;
};

struct Foo
{
  Foo() : p() {} // usual everyday initialization in the ctor-initializer
  union {
    int z;
    double w;
    Point p;
  };
};

int main(void)
{
}

Конечно, размещение new должно по-прежнему использоваться при винификации члена варианта, отличного от другого, инициализированного в конструкторе.

Ответ 2

Этот пример new (&p) Point() - это вызов стандартного размещения new оператора (через новое выражение размещения), поэтому вам нужно включить <new>. Этот конкретный оператор отличается тем, что он не выделяет память, он возвращает только то, что вы ему передали (в данном случае это параметр &p). Конечным результатом выражения является то, что объект был создан.

Если вы комбинируете этот синтаксис с явными вызовами деструктора, вы можете добиться полного контроля над временем жизни объекта:

// Let assume storage_type is a type
// that is appropriate for our purposes
storage_type storage;

std::string* p = new (&storage) std::string;
// p now points to an std::string that resides in our storage
// it was default constructed

// *p can now be used like any other string
*p = "foo";

// Needed to get around a quirk of the language
using string_type = std::string;

// We now explicitly destroy it:
p->~string_type();
// Not possible:
// p->~std::string();

// This did nothing to our storage however
// We can even reuse it
p = new (&storage) std::string("foo");

// Let not forget to destroy our newest object
p->~string_type();

Когда и где вы должны создавать и уничтожать член std::string (пусть его называют s) в вашем классе Value зависит от вашего шаблона использования для s. В этом минимальном примере вы никогда не строите (и, следовательно, уничтожаете) его в специальных членах:

struct Value {
    Value() {}

    Value(Value const&) = delete;
    Value& operator=(Value const&) = delete;

    Value(Value&&) = delete;
    Value& operator=(Value&&) = delete;

    ~Value() {}

    uint64_t lastUpdated;

    union {
        uint64_t ui;
        int64_t i;
        float f;
        bool b;
        std::string s;
    };
};

Таким образом, допустимое использование Value:

Value v;
new (&v.s) std::string("foo");
something_taking_a_string(v.s);
using string_type = std::string;
v.s.~string_type();

Как вы могли заметить, я отключил копирование и перемещение Value. Причиной этого является то, что мы не можем копировать или перемещать соответствующий активный член союза, не зная, какой из них он активен, если он есть.