Вопрос по c++11, constructor, c++ – C ++ 11 анонимный союз с нетривиальными членами

26

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

struct Value {
  uint64_t lastUpdated;

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

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

Since std::string defines all of the six special member functions, U will have an implicitly deleted default constructor, copy constructor, copy assignment operator, move constructor, move assignment operator and destructor. Effectively, this means that you can't create instances of U unless you define some, or all of the special member functions explicitly.

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

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

Однако я использую анонимное объединение в структуре. Я спросил ## C ++ на 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 в этом примере.

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

Мне кажется, что тыдействительн нужно правильновариан ... ildjarn
У меня уже есть вариант класса, который я использую в другом месте. Я не использую его в этом случае, потому что это для сериализованных данных по сети, и я хотел, чтобы данные были небольшими, чтобы все, что Variant оставил внутренним (например, информация о наборе) для класса, который я хотел оставить внешним, и принять решение что на чем основано на схеме пакета. OmnipotentEntity

Ваш Ответ

2   ответа
22

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

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

A Профсоюз как class - это объединение или класс, который имеет анонимный союз в качестве прямого члена. Союзоподобный классX имеет набор Варианты участников. ЕслиX является объединением, его вариантные члены являются нестатическими членами данных; в противном случае его вариантные члены являются нестатическими членами данных всех анонимных объединений, которые являются членамиX.

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

A Т е р-инициализатор может инициализировать вариант члена класса конструктора. Если Т е р-инициализатор указывает более одного Мем-инициализатор для того же члена или для того же базового класса, Т е р-инициализатор плохо сформирован.

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

#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 по-прежнему должно использоваться при добавлении другого элемента, отличного от инициализированного в конструктор

Что будет, еслиFoo конструктор определен, но не выбирает один из этих вариантов членов (то есть,Foo() { } вместо тогоFoo() : p() { })? GCC 5.1 и Clang 3.6 оказываются для компиляции конструктора без каких-либо предупреждений или ошибок: Melpon.org / wandbox / permlink / gLkD49UOrrGhFUJc Тем не менее, неясно, что стандарт говорит об этом случае. dkim
@ dkim: Я уверен, что все члены варианта остаются в одном и том же состоянии, в частности, хранилище получено, но инициализация не выполнена. Раздел 3.8 (время жизни объекта) определяет разрешенные операции для членов в таком состоянии. Ben Voigt
14

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

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

// Let's 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's not forget to destroy our newest object
p->~string_type();

Когда и где вы должны построить и уничтожитьstd::string member (назовем это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. Причина в том, что мы не можем скопировать или переместить соответствующего активного члена объединения, не зная, какой из них является активным, если таковой имеется.

"// Нужно обойти причуду языка" - на самом деле это неправильно - тыможе вызовите деструктор напрямую, если вы все сделаете правильно (необходимо правильно определить область действия):p->std::string::~string();. Тем не менее, более читабельным? Ну, конечно, выглядит сложнее, но использует общеизвестный тип данных, тогда как приведенное выше решение более компактно (кроме дополнительной строки кода дляusing), но вводит редко известный псевдоним. Конечно, дело личного вкуса (что касается меня, я бы проголосовал за известный тип данных ...). Aconcagua

Похожие вопросы