2012-05-21 2 views
24

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

struct Value { 
    uint64_t lastUpdated; 

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

Просто добавив элемент станд :: строковое объединение, конечно, вызывает ошибку компиляции, потому что один обычно нужно добавить нетривиальные конструкторы объекта. In the case of std::string (text from 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 в этом примере.

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

+2

Мне кажется, что вам действительно нужен [вариант] (http://www.boost.org/libs/variant/) ... – ildjarn

+1

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

ответ

21

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

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

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

A union-like class is a union or a class that has an anonymous union as a direct member. A union-like class X has a set of variant members. If X is a union its variant members are the non-static data members; otherwise, its variant members are the non-static data members of all anonymous unions that are members of X .

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

A ctor-initializer may initialize a variant member of the constructor’s class. If a ctor-initializer specifies more than one mem-initializer for the same member or for the same base class, the ctor-initializer is ill-formed.

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

#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) 
{ 
} 

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

+0

Что происходит, если конструктор 'Foo' определен, но не выбирает один из этих вариантов-членов (то есть' Foo() {} 'вместо' Foo(): p() {} ')? GCC 5.1 и Clang 3.6 оказываются вне компиляции конструктора без каких-либо предупреждений или ошибок: http://melpon.org/wandbox/permlink/gLkD49UOrrGhFUJc Однако неясно, что говорит об этом стандарте. – dkim

+0

@dkim: Я уверен, что все члены варианта будут находиться в одном и том же состоянии, в частности, хранение, но инициализация не выполняется. Раздел 3.8 (Object Lifetime) указывает разрешенные операции с членами в таком состоянии. –

13

Этот пример 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 элемент (назовем его 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. Причиной этого является то, что мы не можем копировать или перемещать соответствующего активного члена профсоюза, не зная, какой из них он активен, если таковой имеется.

+5

* «// Нужно обойти причугу языка» * - на самом деле это неправильно - вы * можете * вызвать деструктор напрямую, если вы сделаете это правильно (нужно правильно разрешить область): 'p-> std :: строка :: ~ строка(); '. Более читабельны? Ну, конечно, выглядит более сложным, но использует известный тип данных, тогда как вышеприведенное решение более компактно (кроме дополнительной строки кода для 'use'), но вводит редко известный псевдоним. Разумеется, вопрос личного вкуса (относительно меня, я бы проголосовал за известный тип данных ...). – Aconcagua

Смежные вопросы