class Widget { // Three alternative implementations represented as a unionprivate:
enum class Tag { point, number, text } type; // discriminantunion { // representationpoint p; // point has constructorint i;
string s; // string has default constructor, copy operations, and destructor
};
// ...
widget& operator=(const widget& w) // necessary because of the string variant
{
if (type==Tag::text && w.type==Tag::text) {
s = w.s; // usual string assignmentreturn *this;
}
if (type==Tag::text) s.~string(); // destroy (explicitly!)switch (type==w.type) {
case Tag::point: p = w.p; break; // normal copycase Tag::number: i = w.i; break;
case Tag::text: new(&s)(w.s); break;// placement new
}
type = w.type;
return *this;
}
};
Я выделил не понятные мне места.
point p; — это какого типа переменная? с моей точки зрения это равнозначно написанию:
42 р;
new(&s)(w.s); — такое в операторе присваивания, это бесплатное пособие по UB
Здравствуйте, Caracrist, Вы писали: C>Я выделил не понятные мне места. C>point p; — это какого типа переменная? с моей точки зрения это равнозначно написанию: C>42 р;
Некий пользовательский тип, имеющий пользовательский конструктор (но не деструктор). 42 не идентификатор и не может быть именем типа, поэтому не равнозначно.
C>new(&s)(w.s); — такое в операторе присваивания, это бесплатное пособие по UB
Нормально. У нас ведь не простая структура, а объединение. Когда переключаемся со строки, вызывается деструктор, когда переключаемся на строку — конструктор (напомню, что int — POD, а point не имеет пользовательского деструктора), в частном случае, если до и после строка — оптимизация присваиванием. Никакого UB.
Здравствуйте, rus blood, Вы писали:
RB>Я не понял только вот это C>>
C>> switch (type==w.type) {
C>>
RB>Остальное не вызывает вопросов.
А я вот ещё думаю, что widget и Widget — это разные типы. Поэтому, оператор присваивания у меня вызывает подозрения.
Здравствуйте, gegMOPO4, Вы писали:
C>>new(&s)(w.s); — такое в операторе присваивания, это бесплатное пособие по UB
MOP>Нормально. ... Никакого UB.
Как минимум — последовательность вызовов:
s.~string();
new(&s)(args); // (2)
не является exception-safe безотносительно того, где она располагается. Но здесь ситуация еще хуже — потенциальное исключение в строке 2 не просто оставит объект в несогласованном состоянии, но и приведет к повторному вызову деструктора для объекта 's' при уничтожении объекта типа 'Widget' (а это UB).
Здравствуйте, Юрий Жмеренецкий, Вы писали: ЮЖ>Как минимум — последовательность вызовов: ЮЖ>
ЮЖ>s.~string();
ЮЖ>new(&s)(args); // (2)
ЮЖ>не является exception-safe безотносительно того, где она располагается. Но здесь ситуация еще хуже — потенциальное исключение в строке 2 не просто оставит объект в несогласованном состоянии, но и приведет к повторному вызову деструктора для объекта 's' при уничтожении объекта типа 'Widget' (а это UB).
Нет. Как ни странно, этот хакерский код не содержит ошибок (не считая нескольких уже отмеченных опечаток).
Эта последовательность вызовов никогда не исполнится, случай type==Tag::text && w.type==Tag::text обработан отдельно (потому и без проверки на самоприсваивание можно обойтись). Если в строке (2) выскочит исключение, то объект останется в безопасном состоянии Tag::point или Tag::number.
Здравствуйте, gegMOPO4, Вы писали:
MOP>Эта последовательность вызовов никогда не исполнится, случай type==Tag::text && w.type==Tag::text обработан отдельно (потому и без проверки на самоприсваивание можно обойтись). Если в строке (2) выскочит исключение, то объект останется в безопасном состоянии Tag::point или Tag::number.
new(&s)(args); // thrown exception, oops: type == Tag::text
type = w.type; // never reach this code
//next call if (type==Tag::text) s.~string(); // oops, UB
Здравствуйте, PredatorAlpha, Вы писали:
C>>point p; — это какого типа переменная? с моей точки зрения это равнозначно написанию: C>>42 р;
PA>Tag::point и point — неверно не одно и то же обозначает, да?
Здравствуйте, Caracrist, Вы писали:
C>Здравствуйте, gegMOPO4, Вы писали:
MOP>>Эта последовательность вызовов никогда не исполнится, случай type==Tag::text && w.type==Tag::text обработан отдельно (потому и без проверки на самоприсваивание можно обойтись). Если в строке (2) выскочит исключение, то объект останется в безопасном состоянии Tag::point или Tag::number.
C>
C>new(&s)(args); // thrown exception, oops: type == Tag::text
C>type = w.type; // never reach this code
C>//next call
C>if (type==Tag::text) s.~string(); // oops, UB
C>
При следующем вызове type, по прежнему, будет Tag::point или Tag::number, откуда UB?
C>new(&s)(args); // thrown exception, oops: type == Tag::text
C>
Так в том то и дело, что это случай отдельно обрабатывается, и то, что он тип не изменяет на текст — это хорошо.
Только вот что будет с теми данными, которые там до того хранились в соответствии с другим типом?
Здравствуйте, wvoquine, Вы писали:
W>Так в том то и дело, что это случай отдельно обрабатывается, и то, что он тип не изменяет на текст — это хорошо. W>Только вот что будет с теми данными, которые там до того хранились в соответствии с другим типом?
Скорее всего, ничего хорошего ожидать не стоит, с другой стороны, никто и не обещал строгой гарантии безопасности исключений.
C> class Widget { // Three alternative implementations represented as a union
C> private:
C> enum class Tag { point, number, text } type; // discriminant
C> union { // representation
C> point p; // point has constructor
C> int i;
C> string s; // string has default constructor, copy operations, and destructor
C> };
C> // ...
C> widget& operator=(const widget& w) // necessary because of the string variant
C> {
C> if (type==Tag::text && w.type==Tag::text) {
C> s = w.s; // usual string assignment
C> return *this;
C> }
C> if (type==Tag::text) s.~string(); // destroy (explicitly!)
C> switch (type==w.type) {
C> case Tag::point: p = w.p; break; // normal copy
C> case Tag::number: i = w.i; break;
C> case Tag::text: new(&s)(w.s); break;// placement new
C> }
C> type = w.type;
C> return *this;
C> }
C> };
C>
C>Я выделил не понятные мне места.
C>point p; — это какого типа переменная? с моей точки зрения это равнозначно написанию: C>42 р;
C>new(&s)(w.s); — такое в операторе присваивания, это бесплатное пособие по UB
А меня смущает enum class Tag.
Что это означает? Раньше такого не встречал...
Здравствуйте, Дрободан Фрилич, Вы писали:
ДФ>Здравствуйте, Caracrist, Вы писали:
ДФ>А меня смущает enum class Tag. ДФ>Что это означает? Раньше такого не встречал...
Это из C++11, scoped enumeration. Основные отличия от обычных перечислений это необходимость явной квалификации значений перечисления и отсутствие неявных приведений к числовым типам.
enum class ScopedTag { tag1, tag2, tag3 };
enum UnscopedTag { unscopedTag1, unscopedTag2, unscopedTag3 };
ScopedTag x = ScopedTag::tag1; // ok
ScopedTag y = tag2; // errorint ix = x; // errordouble dx = x; // error
UnscopedTag x_ = unscopedTag1; // ok
UnscopedTag y_ = unscopedTag2; // okint ix_ = x_; // okdouble dx_ = x_; // ok
B0FEE664:
BFE>Здравствуйте, rus blood, Вы писали:
RB>>Я не понял только вот это C>>>
C>>> switch (type==w.type) {
C>>>
RB>>Остальное не вызывает вопросов. BFE>А я вот ещё думаю, что widget и Widget — это разные типы. Поэтому, оператор присваивания у меня вызывает подозрения.
Видимо, у Страуструпа copy-paste-илка сломалась. В N2544
switch (type_ = w.type_)
и widget всюду с маленькой буквы.
Кстати, пользы от этого новшества в C++ что-то не видно.
Здравствуйте, Masterkent, Вы писали: M>Кстати, пользы от этого новшества в C++ что-то не видно.
А шоб було. Просто сняли перестраховочное ограничение, позволили легально стрелять в ногу.
Нужно это очень редко, когда критичны скорость и память (иначе можно было бы использовать обычный динамичный полиморфизм). Например, большой массив лексем в каком-то компиляторе. Или динамический числовой тип. Теперь в качестве кирпичиков можно использовать и готовые сложные типы. Последствия на совести программиста. Многим это не понадобится ни разу в жизни.
gegMOPO4:
M>>Кстати, пользы от этого новшества в C++ что-то не видно.
MOP>Нужно это очень редко, когда критичны скорость и память (иначе можно было бы использовать обычный динамичный полиморфизм).
Все эти хитрые манипуляции с памятью можно выполнять с помощью std::aligned_union или даже std::aligned_storage. Причём аналог boost::variant (который, в отличие от union-ов, достаточно высокоуровневый, чтобы им было удобно пользоваться), насколько я вижу, удобнее реализовывать именно через std::aligned_union, а не union-ы. Иными словами, для низкоуровневых операций вполне сгодится std::aligned_union, для высокоуровневых — какая-нибудь обёртка вокруг std::aligned_union. Чего ради надо было расширять возможности union-ов, непонятно (с учётом того, что std::aligned_union появилось раньше — см. N2369). Ещё интереснее то, что std::aligned_union собирались из стандарта выпилить, но потом одумались