[Trick] безопасный оператор присваивания без swap()
От: 0xDEADBEEF Ниоткуда  
Дата: 28.12.06 19:54
Оценка: 22 (2)
Предлагаю вашему вниманию еще одну вариацию на тему "безопасного" присваивания.
Думаю, всем известна классическая реализация присваивания-через-swap от Герба Саттера: http://www.gotw.ca/gotw/059.htm

Данная реализация также обеспечивает "Strong Exception Guarantee" но не требует реализации функции swap() с "No-Throw Exception Guarantee". Это может быть важно если реализация swap() вызывает трудности. Например, класс аггрегирует один или несколько классов у которых swap() не реализован.

Однако, у этой реализации существует один недостаток чреватый неприятностями: в определенный момент в одном и том же куске памяти ондовременно существуют два экземпляра класса, что явно запрещается стандартом. Сие может вызывать проблемы если класс (или его non-pod data member) используют свой адрес в качестве ключа в какой-либо коллекции...

В общем, если вам надо быстренько реализовать операторы присваивания для кучи классов а писать swap-ы для них геморно или лень, и memcpy() на не-ПОДовском типе вам не режет глаза, используйте на здоровье

ЗЫ.Я сам предпочитаю пользоваться присваиванием-через-swap, но изредка приходится пользоваться и этим...
ЗЗЫ.Этот трюк достаточно грязный и в Бусте ему точно не место, но работать он должен практически везде.

Проверено и работает:
— MSVC 7.1
— GCC 4.1.1 (mingw)

//VC-FLAGS -GX
#include <memory.h>
#include <new>
#include <stdexcept>
#include <sstream>
#include <iostream>

//------------------------------------------------------------------------
//реализация

/*служебный класс*/
template<class T>
struct byteswap_buffer {
    char *dst_,buf_[sizeof(T)];
    
    byteswap_buffer(T& dst)
        :dst_(&reinterpret_cast<char&>(dst))
    {
        memcpy(buf_,dst_,sizeof(buf_));
    }
    
    ~byteswap_buffer()
    {
        memcpy(dst_,buf_,sizeof(buf_));
    }
    
    void swap()
    {
        unsigned int i=sizeof(buf_);
        for(char *a=dst_,*b=buf_; i; --i, ++a, ++b)
        {
            char t = *a;
            *a = *b;
            *b = t;
        }
    }
};

/*Функция испрользующаяся для реализации оператора присваивания. Пример:
TheClass& operator=(TheClass const& src)
{
        return byteswap_assign(*this, src);
}
*/
template<class T, class U>
T& byteswap_assign(T& dst, U& src)
{
    byteswap_buffer<T> buf(dst);
    new((void*)(&reinterpret_cast<char&>(dst))) T(src);
    buf.swap();
    dst.T::~T();
    return dst;
}

//------------------------------------------------------------------------
//пример использования
struct base 
{
    std::string str1;
    std::string str2;
    
    base(std::string name)
        :str1(name)
    {
        std::stringstream s;
        s << std::hex << this;
        str2 = s.str();
    }
    
    void print(std::string note)
    {
        std::cout << note << ':' << '\t' << str1 << '\t' << str2 << std::endl;
    }
};

struct simple: base
{
    simple(std::string name)
        :base(name)
    {
    }
    
    simple(simple const& rhs)
        :base(rhs)
    {
    }
    
    simple& operator=(simple const& rhs)
    {
        return byteswap_assign(*this, rhs);
    }
};

struct throwing_cctor:base
{
    throwing_cctor(std::string name)
        :base(name)
    {
    }
    
    throwing_cctor(throwing_cctor const& rhs)
        :base(rhs)
    {
        throw std::runtime_error("!!!!exception in copy ctor");
    }
    
    throwing_cctor& operator=(throwing_cctor const& rhs)
    {
        return byteswap_assign(*this, rhs);
    }
};

struct throwing_dtor: base
{
    bool throw_;
    ~throwing_dtor()
    {
        if( throw_ )
            throw std::runtime_error("!!!!exception in dtor");
    }
    
    throwing_dtor(std::string name)
        :base(name), throw_(false)
    {
    }
    
    throwing_dtor(throwing_dtor const& rhs)
        :base(rhs), throw_(false)
    {
    }
    
    throwing_dtor& operator=(throwing_dtor const& rhs)
    {
        return byteswap_assign(*this, rhs);
    }
};

int main()
{
    {// проверка обычного присваивания
        simple destination("destination");
        try {
            simple source("source");
            source.print("source before");
            destination.print("destination before");
            destination = source;
        } catch(std::exception const& e) {
            std::cout << e.what() << std::endl;
        }
        destination.print("destination after");
    }
    std::cout << std::endl;
    
    {// проверка присваивания - исключение в CopyContructor'e
        throwing_cctor destination("destination");
        try {
            throwing_cctor source("source");
            source.print("source before");
            destination.print("destination before");
            destination = source;
        } catch(std::exception& e) {
            std::cout << e.what() << std::endl;
        }
        destination.print("destination after");
    }
    std::cout << std::endl;
    
    {// проверка присваивания - исключение в Destructor'e
        throwing_dtor destination("destination");
        try {
            throwing_dtor source("source");
            source.print("source before");
            destination.print("destination before");
            destination.throw_ = true;
            destination = source;
        } catch(std::exception& e) {
            std::cout << e.what() << std::endl;
        }
        destination.print("destination after");
    }
}
__________
16.There is no cause so right that one cannot find a fool following it.
Re: [Trick] безопасный оператор присваивания без swap()
От: Centaur Россия  
Дата: 28.12.06 21:36
Оценка: +2
Здравствуйте, 0xDEADBEEF, Вы писали:

DEA>Предлагаю вашему вниманию еще одну вариацию на тему "безопасного" присваивания.


Можно называть её как угодно, но не «безопасной». Ибо это полный undefined behavior.

DEA>Однако, у этой реализации существует один недостаток чреватый неприятностями: в определенный момент в одном и том же куске памяти ондовременно существуют два экземпляра класса, что явно запрещается стандартом.


Это не запрещено, это просто невозможно. Переиспользование хранилища, отведённого под объект, оканчивает его время жизни (3.8/1).

DEA>template<class T, class U>
DEA>T& byteswap_assign(T& dst, U& src)
DEA>{
// src | dst | buf
// src | old | xxx
DEA>    byteswap_buffer<T> buf(dst);
// src | old | bitwise_copy(old)
DEA>    new((void*)(&reinterpret_cast<char&>(dst))) T(src);
// В этот момент объект old умирает от переиспользования хранилища.
// На его месте рождается новый объект, копия src.
// src | copy(src) | bitwise_copy(old)
// Если копирущий конструктор выбросил исключение:
// src | xxx | bitwise_copy(old)
// деструктор byteswap_buffer попробует вернуть «матрицу личности» old в старое тело:
// src | bitwise_copy(bitwise_copy(old))
DEA>    buf.swap();
// Здесь dst и buf обмениваются. Копия src умирает от переиспользования storage.
// src | bitwise_copy(bitwise_copy(old)) | bitwise_copy(copy(src))
DEA>    dst.T::~T();
// На мёртвую побитовую копию old вызывается деструктор — UB.
// src | xxx | bitwise_copy(copy(src))
DEA>    return dst;
DEA>}
// Здесь деструктор buf копирует содержимое буфера в dst:
// src | bitwise_copy(bitwise_copy(copy(src)))

Таким образом, в результате работы функции byteswap_assign в dst будет побитовая копия побитовой копии объекта, изначально проживавшего по тому же адресу. Для POD’ов такой «переезд» туда-обратно легален и безопасен. Для всех остальных классов — получившийся «гомункул» имеет полное право быть нежизнеспособным.

Люди, не играйте с временем жизни объектов.
Re[2]: [Trick] безопасный оператор присваивания без swap()
От: 0xDEADBEEF Ниоткуда  
Дата: 28.12.06 22:42
Оценка:
Здравствуйте, Centaur, Вы писали:

C>Таким образом, в результате работы функции byteswap_assign в dst будет побитовая копия побитовой копии объекта,

-- Надеюсь вы понимаете, что что класс ОБЯЗАН занимать НЕПРЕРЫВНУЮ область памяти — иначе оператор sizeof() не будет иметь смысла

-- Следовательно, вся информация о экземпляре класса ОБЯЗАНА размещаеться между this и this+sizeof(). Трюки с "классами переменной длины" в расчет не берем, хотя о них и не забываем.

-- Если вышесказанное несправедливо, то техника хранения не-ПОДов, используемая как минимум в std::vector и std::deque, тоже противоречит стандарту

Я, в свою очередь, прекрасно понимаю, что побайтово скопировать класс куда-то и ожидать, что копия будет рабочей крайне наивно. Но... если сохранить побайтовую копию, сделать что-то на месте класса и скоприровать эту копию обратно, то все должно работать.

Например:
X x; /* это сколь угодно сложный класс */
char buf[sizeof(X)];

memcpy(buf, &x, sizeof(X));
memset(&x, 0, sizeof(X));
{
   //(1)здесь ваш код, который не работает с методами и данными инкапсулированными в х, 
   //но может использовать память в диапазоне [x .. x+sizeof X)
   //доступ к buf вам тоже запрещен.
}
memcpy(&x, buf, sizeof(X));
//(2)здесь x должен остаться в рабочем состянии


И теперь мой к вам вопрос:
— Предложите код класса X и код в точке (1), который приведет к тому, что экземпляр x в точке (2) будет в нерабочем состоянии.
— Код должен демонстрировать "эффект" (т.е некорректный результат в точке (2)) хотя бы на одном из компиляторов: GCC, Ms Visual, Borland, Intel, Digital Mars.
— Использовать эффект "два класса на одном и том же адресе", описанный мною в предыдущем посте, считается неспортивным

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

C>Люди, не играйте с временем жизни объектов.

Угу — "Умный в горы не пойдет — умный горы обойдет"...
__________
16.There is no cause so right that one cannot find a fool following it.
Re: [Trick] безопасный оператор присваивания без swap()
От: Tonal- Россия www.promsoft.ru
Дата: 29.12.06 07:14
Оценка: 4 (1) +1
Здравствуйте, 0xDEADBEEF, Вы писали:

DEA>- GCC 4.1.1 (mingw)

А где берут GCC 4.1.1 (mingw)?
На сайте последняя версия — 3.4.5 — сандидат...
Re[3]: [Trick] безопасный оператор присваивания без swap()
От: Аноним  
Дата: 29.12.06 08:27
Оценка: :)
Здравствуйте, 0xDEADBEEF, Вы писали:

DEA>Например:

DEA>
DEA>X x; /* это сколь угодно сложный класс */
DEA>char buf[sizeof(X)];

DEA>memcpy(buf, &x, sizeof(X));
DEA>memset(&x, 0, sizeof(X));
DEA>{
DEA>   //(1)здесь ваш код, который не работает с методами и данными инкапсулированными в х, 
DEA>   //но может использовать память в диапазоне [x .. x+sizeof X)
DEA>   //доступ к buf вам тоже запрещен.
DEA>}
DEA>memcpy(&x, buf, sizeof(X));
DEA>//(2)здесь x должен остаться в рабочем состянии
DEA>


DEA>И теперь мой к вам вопрос:

DEA>- Предложите код класса X и код в точке (1), который приведет к тому, что экземпляр x в точке (2) будет в нерабочем состоянии.
DEA>- Код должен демонстрировать "эффект" (т.е некорректный результат в точке (2)) хотя бы на одном из компиляторов: GCC, Ms Visual, Borland, Intel, Digital Mars.
DEA>- Использовать эффект "два класса на одном и том же адресе", описанный мною в предыдущем посте, считается неспортивным

И толку, если в классе будет хотя бы один член класса в виде указателя на обьект создаваемый динамически — то все ваше копирование рухнет, так как будет скопирован просто указатель на старый обьект, а не создастся новый
Re[3]: [Trick] безопасный оператор присваивания без swap()
От: remark Россия http://www.1024cores.net/
Дата: 29.12.06 09:16
Оценка: :)
Здравствуйте, 0xDEADBEEF, Вы писали:

DEA>-- Надеюсь вы понимаете, что что класс ОБЯЗАН занимать НЕПРЕРЫВНУЮ область памяти — иначе оператор sizeof() не будет иметь смысла


Ну вот тут ты как раз и не прав:

1.8/5
An object of POD type shall occupy contiguous bytes of storage.


Про не POD типы никаких таких гарантий нет. И это не случайно, собственно Страуструп специально настаивал, что бы таких гарантий и не было для не POD типов, т.к. необходимости давать такие гарантии для классов нет, и это открывает гораздо больше возможностей для языка, причём возможностей очень интересных.
Почему нет необходимости в таких гарантиях — объекты всё равно больше нельзя копировать memcpy, конструктор копирования способен скопировать один набор областей памяти в другой набор областей памяти.
С возможностями интереснее. Кочено всё мы знаем, что объекты во всех наших реализациях языка занимают непрерывные области. Это факт. Если уж мы даже на это не полагаемся, то по крайней мере так себе подсознательно представляем объекты.
Однако! Сейчас появилсь первый потенциальный кандидат на действительно не непрерывные объекты! Это — mixed типы в c++/cli. Идея такая — часть объекта находится в свободной памяти с++, часть объекта находится в куче cli. Эти две части связаны спецальными "связками" (на с++ часть объекта указывает простой указатель, на cli часть — некий прокси). И конструктор копирования/оператор равно _сможет_ с этим справляться! Страуструп просто прыгал от радости, когда узнал, что его фиче всё-таки найдётся применение, и что он такой умный



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: [Trick] безопасный оператор присваивания без swap()
От: remark Россия http://www.1024cores.net/
Дата: 29.12.06 09:17
Оценка:
Здравствуйте, Аноним, Вы писали:

А>И толку, если в классе будет хотя бы один член класса в виде указателя на обьект создаваемый динамически — то все ваше копирование рухнет, так как будет скопирован просто указатель на старый обьект, а не создастся новый


Вы код глядели?


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: [Trick] безопасный оператор присваивания без swap()
От: remark Россия http://www.1024cores.net/
Дата: 29.12.06 09:28
Оценка:
Здравствуйте, Centaur, Вы писали:

C>Здравствуйте, 0xDEADBEEF, Вы писали:


DEA>>Предлагаю вашему вниманию еще одну вариацию на тему "безопасного" присваивания.


C>Можно называть её как угодно, но не «безопасной». Ибо это полный undefined behavior.


Давайте будем как-то более аргументированными и не будем оперировать исключительно абстрактными понятиями.
Я предлагаю такую постановку вопроса: в каких случаях (для каких классов или вариантов их использования) это не будет работать, в каких текущих реализациях языка (на каких компиляторах), или для каких фич возможна неработоспособность в будущих реализациях языка.

Например вот вариант. Если объект будет занимать не непрерывную область памяти.
Сейчас таких реализаций нет. Это факт. И я очень-очень сильно сомневаюсь, что в будущем для чистого "с++" такие реализации появятся.

Из более-менее реальных ситуаций я вижу только такую.
Объекты в конструкторе регистрируют себя в неком глобальном регистре. И при добавлении нового объекта в регистр, регистр "пробегает" по всем предыдущим зарегистрированным объектам, чтобы, допустим, их уведомить их о появлении нового объекта.
Вот тут, действительно, всё навернётся.

Больше ветоятных ситуаций пока на ум не приходит.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: [Trick] безопасный оператор присваивания без swap()
От: 0xDEADBEEF Ниоткуда  
Дата: 29.12.06 11:30
Оценка:
Здравствуйте, Tonal-, Вы писали:

T>Здравствуйте, 0xDEADBEEF, Вы писали:


DEA>>- GCC 4.1.1 (mingw)

T>А где берут GCC 4.1.1 (mingw)?
Компилируют своими силами. Если установлена MSYS, ессесна.
Процедура расписана в <gcc-src-root>/INSTALL
Единственное, что нужно сделать дополнительно, это скопировать виндовые заголовки и библиотеки (пакет w32api для mingw) в каталог <gcc-src-root>/winsup/w32api
__________
16.There is no cause so right that one cannot find a fool following it.
Re[3]: [Trick] безопасный оператор присваивания без swap()
От: 0xDEADBEEF Ниоткуда  
Дата: 29.12.06 11:59
Оценка: 3 (2) :))
Здравствуйте, remark, Вы писали:

R>Я предлагаю такую постановку вопроса: в каких случаях (для каких классов или вариантов их использования) это не будет работать, в каких текущих реализациях языка (на каких компиляторах), или для каких фич возможна неработоспособность в будущих реализациях языка.


R>Например вот вариант. Если объект будет занимать не непрерывную область памяти.

R>Сейчас таких реализаций нет. Это факт. И я очень-очень сильно сомневаюсь, что в будущем для чистого "с++" такие реализации появятся.

R>Из более-менее реальных ситуаций я вижу только такую.

R>Объекты в конструкторе регистрируют себя в неком глобальном регистре. И при добавлении нового объекта в регистр, регистр "пробегает" по всем предыдущим зарегистрированным объектам, чтобы, допустим, их уведомить их о появлении нового объекта.
Собственно, об этом я и предупреждал...

R>Вот тут, действительно, всё навернётся.

Если используются "обычные" средства управления памятью, то все будет нормально, кроме того, что произойдет попытка регистрации экземпляра с тем же самым адресом.
Реакция на это одна из:
— регистр плюнет исключением при попытке такой регистрации — присваивания не произойдет
— регистр произведет регистрацию с новыми данными — в итоге деструктор "старого" екземпляра эту регистрацию прибьет и "новый" экземпляр окажется незарегистрированным.
— регистр зарегистрирует "дубликат" — в этом случае все становится крайне гадко ибо неизвестно какую регистрационную запись прибьет деструктор "старого" экземпляра.

...Впрочем, есть еще одна ба-а-альшая гадость: если класс использует недетерминированную сборку мусора (типа Garbage Collector-а от Hans-а Boehm-а), то при копировании в буфер могут потеряться GC-root-ы. И, если в конструторе копирования начнется цикл сборки мусора, куски памяти, на которые ссылается "старый" экземпляр могут быть прибиты, что вызовет проблемы в деструкторе "старого" экземпляра...

Хотя... с точки зрения Кентавра, боэмовский GC это сплошь "Undefined Behavior"
...Впрочем как и Кентавры с точки зрения биологии

R>

__________
16.There is no cause so right that one cannot find a fool following it.
Re[3]: [Trick] безопасный оператор присваивания без swap()
От: Константин Л. Франция  
Дата: 29.12.06 13:36
Оценка:
Здравствуйте, remark, Вы писали:

я может быть не правильно понял код, но что делать с объектами, у которых оператор присваивания нетривиальный (подсчет ссылок etc.) и может, кинув исключение, оставить объект в несогласованном состоянии? Не меняем ли мы шило на мыло?
Re: [Trick] безопасный оператор присваивания без swap()
От: _Winnie Россия C++.freerun
Дата: 02.01.07 21:42
Оценка:
Здравствуйте, 0xDEADBEEF, Вы писали:

1)
Почему так сложно написано? Почему не просто

template <class T> binary_swap(T &a, T &b) 
{ 
  for (size_t i = 0; i < sizeof(T); ++i)
     std::swap(reinterpret_cast<unsigned char *>(&a)[i], reinterpret_cast<unsigned char *>(&b)[i]);     
}


void A::swap(A &a) { binary_swap(a, *this); }

A &A::operator=(const A &a)
{   
   A(a).swap(*this);
   return *this;
}



2)
будут проблемы с виртуальным наследованием в g++ (в том ABI, что у него под *nix)- внутри объекта ссылка на него самого (точнее, его виртуальные базы).
Правильно работающая программа — просто частный случай Undefined Behavior
Re[2]: [Trick] безопасный оператор присваивания без swap()
От: _Winnie Россия C++.freerun
Дата: 02.01.07 21:57
Оценка: :))
Здравствуйте, _Winnie, Вы писали:

_W>Здравствуйте, 0xDEADBEEF, Вы писали:


_W>1)

_W>Почему так сложно написано? Почему не просто
_W>[/ccode]
_W>2)
_W>будут проблемы с виртуальным наследованием в g++ (в том ABI, что у него под *nix)- внутри объекта ссылка на него самого (точнее, его виртуальные базы).

Претензии снимаются. Очень ловко придумано.
Правильно работающая программа — просто частный случай Undefined Behavior
Re[3]: [Trick] безопасный оператор присваивания без swap()
От: remark Россия http://www.1024cores.net/
Дата: 02.01.07 22:03
Оценка:
Здравствуйте, _Winnie, Вы писали:

_W>Претензии снимаются. Очень ловко придумано.


Я тоже долго над этим медитировал

template<class T, class U>
T& byteswap_assign(T& dst, U& src)
{
    byteswap_buffer<T> buf(dst);
    new((void*)(&reinterpret_cast<char&>(dst))) T(src);
    buf.swap();
    dst.T::~T();
    return dst;
}




1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: [Trick] безопасный оператор присваивания без swap()
От: 0xDEADBEEF Ниоткуда  
Дата: 02.01.07 22:14
Оценка: :)
Здравствуйте, remark, Вы писали:

_W>>Претензии снимаются. Очень ловко придумано.

R>Я тоже долго над этим медитировал
...Это как оттрахать любимую женщину на театральной люстре...
Одни скажут что "пошло", а другие не поверят
__________
16.There is no cause so right that one cannot find a fool following it.
Re[3]: [Trick] безопасный оператор присваивания без swap()
От: Кодт Россия  
Дата: 06.01.07 10:46
Оценка: 4 (1) +1
Здравствуйте, remark, Вы писали:

C>>Можно называть её как угодно, но не «безопасной». Ибо это полный undefined behavior.


R>Давайте будем как-то более аргументированными и не будем оперировать исключительно абстрактными понятиями.

R>Я предлагаю такую постановку вопроса: в каких случаях (для каких классов или вариантов их использования) это не будет работать, в каких текущих реализациях языка (на каких компиляторах), или для каких фич возможна неработоспособность в будущих реализациях языка.

Ещё один вариант, где это может нездорово аукнуться. При наследовании полиморфного класса.
template<class Dst, class Src> void reconstruct(Dst& dst, Src const& src)
{
    // не будем сейчас увлекаться устойчивостью к исключениям
    // выделим суть
    dst.~Dst();
    new(&dst) Dst(src);
}

struct Base
{
    virtual foo() {}

#ifdef VIRT_DTOR
    virtual ~Base() {}
#endif

#ifdef RECONSTRUCT
    Base& operator=(const Base& src) { reconstruct(*this,src); }
#endif
};

struct Derived : /* возможно, очень далёкий потомок от */ Base
{
    string s;
    virtual foo() {}
};

int main()
{
    Derived d1, d2;
    d1 = d2;
    d1.foo();
    d1.s = "";
}

Присваивание выполнит Derived::operator=(Derived const&), из него Base::operator=(Base const&).

Если RECONSTRUCT включен, то на месте базового подобъекта (Base)d1 будет сконструирован новый Base, с соответствующей настройкой виртуальных функций.
И d1.foo() выполнит Base::foo().
Это первое UB.

Кроме того, если ещё и VIRT_DTOR, то будет разрушен весь наследник, а затем возрождён только базовый подобъект. Доступ к полям наследника — d1.s — уже невозможен.
Вот и второе UB.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[4]: [Trick] безопасный оператор присваивания без swap()
От: remark Россия http://www.1024cores.net/
Дата: 06.01.07 11:29
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Ещё один вариант, где это может нездорово аукнуться. При наследовании полиморфного класса.


Если я всё правильно понял, то к byteswap_assign это никакого отношения не имеет, это просто пример плохого кода...

К>Это первое UB.

К>Вот и второе UB.

Это уже не UB, это уже "прощай мама"


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[5]: [Trick] безопасный оператор присваивания без swap()
От: Кодт Россия  
Дата: 06.01.07 12:36
Оценка:
Здравствуйте, remark, Вы писали:

R>Если я всё правильно понял, то к byteswap_assign это никакого отношения не имеет, это просто пример плохого кода...


Как раз имеет. reconstruct — это упрощённая версия byteswap_assign, не устойчивая к исключениям.
Конечно, можно указать, что класс с byteswap_assign-присваиванием запаян (от него запрещено наследоваться)...

К>>Это первое UB.

К>>Вот и второе UB.

R>Это уже не UB, это уже "прощай мама"


Это — "хорошее" UB, которое заведомо ведёт к краху. В отличие от "легальных", про которые известно, что в данной реализации так делать можно и "половинчатых", которые могут прокатить в некоторых тепличных условиях.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[4]: [Trick] безопасный оператор присваивания без swap()
От: _Winnie Россия C++.freerun
Дата: 06.01.07 13:51
Оценка:
Здравствуйте, Кодт, Вы писали:




К>Кроме того, если ещё и VIRT_DTOR, то будет разрушен весь наследник, а затем возрождён только базовый подобъект. Доступ к полям наследника — d1.s — уже невозможен.

К>Вот и второе UB.

К> dst.~Dst();
Изначально было dst.Dst::~Dst();


К>Если RECONSTRUCT включен, то на месте базового подобъекта (Base)d1 будет сконструирован новый Base, с соответствующей настройкой виртуальных функций.

К>И d1.foo() выполнит Base::foo().
К>Это первое UB.
Это уже очень неприятно, да...
Правильно работающая программа — просто частный случай Undefined Behavior
Re[6]: [Trick] безопасный оператор присваивания без swap()
От: remark Россия http://www.1024cores.net/
Дата: 06.01.07 14:25
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, remark, Вы писали:


R>>Если я всё правильно понял, то к byteswap_assign это никакого отношения не имеет, это просто пример плохого кода...


К>Как раз имеет. reconstruct — это упрощённая версия byteswap_assign, не устойчивая к исключениям.

К>Конечно, можно указать, что класс с byteswap_assign-присваиванием запаян (от него запрещено наследоваться)...

Догнал
Ну да, можно либо указать seal (либо даже форсировать, тогда не придирёшься)
Либо сделать проверку для полиморфных классов, типа такого:

// Проверяем, что не порушили динамический тип объекта
ASSERT(*reinterpret_cast<int*>(this) == *reinterpret_cast<int*>(buf));




К>>>Это первое UB.

К>>>Вот и второе UB.

R>>Это уже не UB, это уже "прощай мама"


К>Это — "хорошее" UB, которое заведомо ведёт к краху. В отличие от "легальных", про которые известно, что в данной реализации так делать можно и "половинчатых", которые могут прокатить в некоторых тепличных условиях.


Ну да, это я и имел в виду
Целевое поведение — подмножество UB. Полюбому


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[7]: [Trick] безопасный оператор присваивания без swap()
От: Кодт Россия  
Дата: 06.01.07 15:37
Оценка:
Здравствуйте, remark, Вы писали:

R>Ну да, можно либо указать seal (либо даже форсировать, тогда не придирёшься)

R>Либо сделать проверку для полиморфных классов, типа такого:
R>// Проверяем, что не порушили динамический тип объекта
R>ASSERT(*reinterpret_cast<int*>(this) == *reinterpret_cast<int*>(buf));

Ну только не так! Расположение (да и вообще наличие) vfptr в теле объекта — очень изменчиво. А если это POD-структура, чьё первое поле — как раз int?

Правильнее — сравнивать typeid'ы.
ASSERT(typeid(dst)==typeid(src));

И более того, по факту неравенства можно выполнять обычное присваивание без фокусов.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[8]: [Trick] безопасный оператор присваивания без swap()
От: remark Россия http://www.1024cores.net/
Дата: 06.01.07 16:07
Оценка: :)
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, remark, Вы писали:


R>>Ну да, можно либо указать seal (либо даже форсировать, тогда не придирёшься)

R>>Либо сделать проверку для полиморфных классов, типа такого:
К>
R>>// Проверяем, что не порушили динамический тип объекта
R>>ASSERT(*reinterpret_cast<int*>(this) == *reinterpret_cast<int*>(buf));
К>

К>Ну только не так! Расположение (да и вообще наличие) vfptr в теле объекта — очень изменчиво. А если это POD-структура, чьё первое поле — как раз int?

Речь же идёт о создании некого класса, у которого мы реализуем operator= таким хитрым способом.
Если это класс с виртуальными функциями и предполагает наследование, то имхо можно сделать как я написал.
Т.е. всё это делается в комплексе.
(правда вопрос по поводу множественного наследования... может там что будет не так)


К>Правильнее — сравнивать typeid'ы.

К>
К>ASSERT(typeid(dst)==typeid(src));
К>

К>И более того, по факту неравенства можно выполнять обычное присваивание без фокусов.


Ну собственно фишка вроде изначально и была в том, что нам лень делать нормальный swap или писать "хороший" operator=. А уж если мы его всё равно напишем, то смысл тогда использовать кривой


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.