Вопрос к профи.
От: TepMuHyc  
Дата: 21.01.03 14:41
Оценка: 2 (1)
Вот... Сотворил такой assignment.

my_class& operator=(const my_class& rhs)
{
   struct{ char data[sizeof(*this)]; } tmp;
   new(&tmp) my_class(rhs); //создаем новый временный экземпляр класса в буфере tmp.
   this->~my_class();//вызываем деструктор на текущем экземпляре.
   memcpy(this, &tmp, sizeof(*this));//бинарно копируем временный экземпляр в текущий.
   return *this;
}


Скажу сразу: класс безопасен для бинарного копирования — т.е. он не содержит ссылок и указателей на самого себя.
По exception-safety все тоже прокатывает на ура — единственное место где может вылезти исключение — это конструктор копирования — в этом случае недоделанный временный экземпляр будет автоматически прибит а текущий экземпляр сохранит свое сотояние.

Но... Гложут смутные сомнения: все так просто и прозрачно, но я нигде еще не видел такого подхода.
Значит, ДОЛЖНЫ быть грабли. Вопрос ГДЕ?
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Re: Вопрос к профи.
От: Аноним  
Дата: 21.01.03 14:44
Оценка:
А если деструктор виртуален и у тебя есть производные от твоего класса?
Re[2]: Вопрос к профи.
От: TepMuHyc  
Дата: 21.01.03 14:48
Оценка:
Здравствуйте, Аноним, Вы писали:

А>А если деструктор виртуален и у тебя есть производные от твоего класса?

1) виртуальный деструктор он и в африке виртуальный.
2) оператор привсаивания не наследуется.
3) :down: :down: :down: (догадайся почему)
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Re: Вопрос к профи.
От: IT Россия linq2db.com
Дата: 21.01.03 14:50
Оценка:
Здравствуйте, TepMuHyc, Вы писали:

TMH>Вот... Сотворил такой assignment.


А зачем тогда вообще временная структура? Пиши прямо в сам класс ;)

my_class& operator=(const my_class& rhs)
{
   new(this) my_class(rhs); //создаем новый временный экземпляр класса в буфере tmp.
   return *this;
}
Если нам не помогут, то мы тоже никого не пощадим.
Re[3]: Вопрос к профи.
От: Аноним  
Дата: 21.01.03 14:51
Оценка:
Здравствуйте, TepMuHyc, Вы писали:

TMH>2) оператор привсаивания не наследуется.


Это неправда. Он наследуется, но, как правило, скрывается в производном классе.
Re: Вопрос к профи.
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 21.01.03 14:53
Оценка: 4 (1)
Здравствуйте, TepMuHyc, Вы писали:

TMH>Вот... Сотворил такой assignment.

Сам придумал ?

my_class& operator=(const my_class& rhs)
{
 struct{ char data[sizeof(*this)]; } tmp;

 new(&tmp) my_class(rhs); //создаем новый временный экземпляр класса в буфере tmp.

 this->~my_class();//вызываем деструктор на текущем экземпляре.
 memcpy(this, &tmp, sizeof(*this));//бинарно копируем временный экземпляр в текущий.
 return *this;
}


TMH>Значит, ДОЛЖНЫ быть грабли. Вопрос ГДЕ?


В классах (структурах), в которых sizeof не возвращает реальный размер объекта. Примером являются всякие динамически создаваемые конструкции, которые держат свои данные за последним официальным байтом объекта. То есть содержат оператор new типа такого:

void* operator new (size_t sz,size_type data_size)
{
 return ::operator new(sz+data_size);
}


Я прав?
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re: Вопрос к профи.
От: Павел Кузнецов  
Дата: 21.01.03 14:54
Оценка:
Здравствуйте, TepMuHyc, Вы писали:

TMH>ДОЛЖНЫ быть грабли. Вопрос ГДЕ?


TMH>
TMH>my_class& operator=(const my_class& rhs)
TMH>{
TMH>   struct{ char data[sizeof(*this)]; } tmp;
TMH>   new(&tmp) my_class(rhs); //создаем новый временный экземпляр класса в буфере tmp.
TMH>   this->~my_class();//вызываем деструктор на текущем экземпляре.
TMH>   memcpy(this, &tmp, sizeof(*this));//бинарно копируем временный экземпляр в текущий.
TMH>


^ Здесь: 1) стандарт гарантирует возможность memcpy в массив char только для POD-типов; 2) alignment requirements my_class в массиве char, определенном "на стеке", могут не соблюдаться. Undefined behavior.

TMH>
TMH>   return *this;
TMH>}
TMH>
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[3]: Вопрос к профи.
От: Аноним  
Дата: 21.01.03 14:55
Оценка:
Ты сам
А если в операторе производного класса
Derived & operator = (const Derived & rhs){
if(this != rhs){
Base::operator = (rhs);
//something else
}
return *this;
}
Re[2]: Вопрос к профи.
От: kmn Украина  
Дата: 21.01.03 15:02
Оценка:
Здравствуйте, Аноним, Вы писали:

А>А если деструктор виртуален и у тебя есть производные от твоего класса?


то можно написать так:


this->my_class::~my_class();


и не важно виртуальный деструктор или нет!!!

и получается


my_class& operator=(const my_class& rhs)
{
   this->my_class::~my_class();
   new(this) my_class(rhs);
   return *this;
}
Re[3]: Вопрос к профи.
От: Аноним  
Дата: 21.01.03 15:07
Оценка:
А теперь смотри 3.8.7
Re[2]: Вопрос к профи.
От: TepMuHyc  
Дата: 21.01.03 15:45
Оценка:
Здравствуйте, Коваленко Дмитрий, Вы писали:


TMH>>Значит, ДОЛЖНЫ быть грабли. Вопрос ГДЕ?

КД>В классах (структурах), в которых sizeof не возвращает реальный размер объекта.
КД>Я прав?
И да и нет :-)
Вот почему: такие обьекты не работают как "нормальные обьекты" — т.е. не могут создаваться на стеке
и не могут быть созданы через "нормальный" оператор new

Из-за этого, как правило, конструктор у них не-public (чтобы кто-нить) не создал их
по ошибке как "нормальный" обьект. А создаются такие обьекты через функции-фабрики.

Кстати, как написать "классический" оператор присваивания для таких обьектов — лично я не имею понятия :-)

А вообще, грабли вполне конкретные. Спасибо.
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Re[3]: Вопрос к профи.
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 21.01.03 15:55
Оценка:
Здравствуйте, TepMuHyc, Вы писали:

TMH>>>Значит, ДОЛЖНЫ быть грабли. Вопрос ГДЕ?

КД>>В классах (структурах), в которых sizeof не возвращает реальный размер объекта.

TMH>Кстати, как написать "классический" оператор присваивания для таких обьектов — лично я не имею понятия


Я тоже не знаю. Как присвоить маленький объект большому, вроде понятно. Наоборот — нет

Кстати, под запрет бинарного копирования попадают объекты со счетчиком внешних ссылок. Вспомнил вот.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re: Вопрос к профи.
От: VVV Россия  
Дата: 21.01.03 16:01
Оценка:
Здравствуйте, TepMuHyc, Вы писали:

..
TMH>Но... Гложут смутные сомнения: все так просто и прозрачно, но я нигде еще не видел такого подхода.

Видел подход наоборот — создаётся нормальный оператор присваивания, а конструктор копирования определяется через этот оператор (хотя, как я понимаю, тебе понравилась идея: дескруктор себе->конструктор себе->бинарная копия).

Borland OWL:

//
// Make a copy of the context entry.
//
THelpContext::THelpContext(const THelpContext& other)
{
  *this = other;
}

//
// Make a copy of the context entry.
//
THelpContext&
THelpContext::operator =(const THelpContext& other)
{
  HelpFileContextId = other.GetHelpFileContextId();
...


VC MFC:

COleSafeArray::COleSafeArray(const COleSafeArray& saSrc)
{
    AfxSafeArrayInit(this);
    *this = saSrc;
    m_dwDims = GetDim();
    m_dwElementSize = GetElemSize();
}
Re[2]: Вопрос к профи.
От: TepMuHyc  
Дата: 21.01.03 16:11
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>1) стандарт гарантирует возможность memcpy в массив char только для POD-типов;

Совершенно согласен — memcpy ересь есмь :-).
Но практически — если данные класса имеют ссылок на другие данные класса — все работает.
Причем, практически на всех компиляторах.

Кстати, на comp.lang.c++.moderated подобные вещи (с оговорками, конечно) появляются довольно часто.
Последний раз видел это дело от Александреску.

ПК>2) alignment requirements my_class в массиве char, определенном "на стеке", могут не соблюдаться.

А как по поводу алайнмента структуры (читай ПОД-а) на стеке? Я ж буфер определяю как
"struct{ char data[sizeof(*this)]; } tmp;".
Или алайнмент для ПОДов и не-ПОДов разный ?
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Re[3]: Вопрос к профи.
От: Павел Кузнецов  
Дата: 21.01.03 16:18
Оценка:
Здравствуйте, TepMuHyc, Вы писали:

ПК>>2) alignment requirements my_class в массиве char, определенном "на стеке", могут не соблюдаться.


TMH>А как по поводу алайнмента структуры <...> Или алайнмент для ПОДов и не-ПОДов разный ?


Alignment requirements для двух разных классов (POD/non-POD — не важно) могут различаться.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re: Вопрос к профи.
От: MaximE Великобритания  
Дата: 21.01.03 16:21
Оценка: 3 (1)
Здравствуйте, TepMuHyc, Вы писали:

TMH>По exception-safety все тоже прокатывает на ура — единственное место где может вылезти исключение — это конструктор копирования — в этом случае недоделанный временный экземпляр будет автоматически прибит а текущий экземпляр сохранит свое сотояние.


Намного безопаснее пользоваться идиомой обмена с временным объектом, а не побитовым копированием. Метод Swap() гарантировано не бросает исключений. Получаем строгую гарантию безопасности исключений:

template<class T>
class ReferencePointerBase
{
public:
    T& operator*() { return *_pt; }
    const T& operator*() const { return *_pt; }

    T* operator->() { return _pt; }
    const T* operator->() const { return _pt; }

    T* get() const { return _pt; }

    bool operator!() const { return !_pt; }

protected:
    ReferencePointerBase(T* t = 0) : _pt(t) { Increment(); }
    ReferencePointerBase(const ReferencePointerBase& p) : _pt(p._pt) { Increment(); }
    template<class U> ReferencePointerBase(const ReferencePointerBase<U>& p) : _pt(p._pt) { Increment(); }

    ReferencePointerBase& operator=(T* t) { ReferencePointerBase(t).Swap(*this); return *this; }
    ReferencePointerBase& operator=(const ReferencePointerBase& p) { ReferencePointerBase(p).Swap(*this); return *this; }
    template<class U> ReferencePointerBase& operator=(const ReferencePointerBase<U>& p) { ReferencePointerBase(p).Swap(*this); return *this; }

    ~ReferencePointerBase() { if (_pt) { _pt->Release(); _pt = 0; } }

    friend class ReferencePointerBase;
    template<class U> Swap(ReferencePointerBase<U>& p) { std::swap(_pt, p._pt); }
    void Increment() { if (_pt) _pt->AddRef(); }
    T* _pt;
};


Внесены изменения автора. -- ПК.
Re[2]: Вопрос к профи.
От: TepMuHyc  
Дата: 21.01.03 16:42
Оценка:
Здравствуйте, VVV, Вы писали:

VVV>Видел подход наоборот — создаётся нормальный оператор присваивания, а конструктор копирования определяется через этот оператор (хотя, как я понимаю, тебе понравилась идея: дескруктор себе->конструктор себе->бинарная копия).


Вот почему мне не нравится такой подход: Конструктор создает обьект на пустом месте.
А оператор присваивания обязан корректно уничтожить предыдущее состояние обьекта и скопировать новое — что несколько сложнее.

Поэтому, конструктор копирования перед вызовом оператора присваивания обязан выполнить инициализацию обьекта — по сути дела вызвать конструктор по-умолчанию. Иначе оператор присваивания с треском навернется.
Налицо никому не нужная работа.
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Re[3]: Вопрос к профи.
От: TepMuHyc  
Дата: 21.01.03 16:51
Оценка:
Здравствуйте, kmn, Вы писали:

kmn>
kmn>my_class& operator=(const my_class& rhs)
kmn>{
kmn>   this->my_class::~my_class();
kmn>   new(this) my_class(rhs);
kmn>   return *this;
kmn>}
kmn>

Вот так — низзя — к чертям летит exception-safety.

Можно конечно так:
my_class& operator=(const my_class& rhs)
{
   this->my_class::~my_class();
   try {
      new(this) my_class(rhs);
   }catch(...){
      new(this) my_class();//вот здесь try/catch не надо.
         //лучше поиметь unexpected() сразу, чем удаление 
         //непроинициализированного обьекта потом. 
      throw;
   }
   return *this;
}

Но данный подход накладывает одно ограничение — конструктор
по-умолчанию не должен бросаться исключениями.
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Re: Вопрос к профи.
От: MaximE Великобритания  
Дата: 21.01.03 20:26
Оценка: 9 (2)
Здравствуйте, TepMuHyc, Вы писали:

TMH>Но... Гложут смутные сомнения: все так просто и прозрачно, но я нигде еще не видел такого подхода.

TMH>Значит, ДОЛЖНЫ быть грабли. Вопрос ГДЕ?

Это антиидиома. Граблей здесь намного больше чем преимуществ.

Problem #1: It Can Slice Objects
Problem #2: It's Not Exception-Safe
Problem #3: It's Inefficient for Assignment
Problem #4: It Changes Normal Object Lifetimes
Problem #5: It Can Still Break Derived Classes
Problem #6: It Relies on Unreliable Pointer Comparisons (но этого у тебя нет)


Подробности: Object Lifetimes &mdash; Part II
Re[4]: Вопрос к профи.
От: Аноним  
Дата: 22.01.03 08:10
Оценка:
Что ты за бред городишь? Могу еще один дельный совет из разряда твоих идей насчет оператора присваивания дать, столь же мощное решение:
ты с конструктором копий не парься, а сразу пиши
*this = rhs;
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.