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
Оценка:
А если деструктор виртуален и у тебя есть производные от твоего класса?
Здравствуйте, Аноним, Вы писали:
А>А если деструктор виртуален и у тебя есть производные от твоего класса?
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.
Здравствуйте, 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 типа такого:
Здравствуйте, 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;
}
TMH>>Значит, ДОЛЖНЫ быть грабли. Вопрос ГДЕ? КД>В классах (структурах), в которых sizeof не возвращает реальный размер объекта. КД>Я прав?
И да и нет :-)
Вот почему: такие обьекты не работают как "нормальные обьекты" — т.е. не могут создаваться на стеке
и не могут быть созданы через "нормальный" оператор new
Из-за этого, как правило, конструктор у них не-public (чтобы кто-нить) не создал их
по ошибке как "нормальный" обьект. А создаются такие обьекты через функции-фабрики.
Кстати, как написать "классический" оператор присваивания для таких обьектов — лично я не имею понятия :-)
А вообще, грабли вполне конкретные. Спасибо.
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Здравствуйте, TepMuHyc, Вы писали:
TMH>>>Значит, ДОЛЖНЫ быть грабли. Вопрос ГДЕ? КД>>В классах (структурах), в которых sizeof не возвращает реальный размер объекта.
TMH>Кстати, как написать "классический" оператор присваивания для таких обьектов — лично я не имею понятия
Я тоже не знаю. Как присвоить маленький объект большому, вроде понятно. Наоборот — нет
Кстати, под запрет бинарного копирования попадают объекты со счетчиком внешних ссылок. Вспомнил вот.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
.. 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();
...
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>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.
Здравствуйте, TepMuHyc, Вы писали:
ПК>>2) alignment requirements my_class в массиве char, определенном "на стеке", могут не соблюдаться.
TMH>А как по поводу алайнмента структуры <...> Или алайнмент для ПОДов и не-ПОДов разный ?
Alignment requirements для двух разных классов (POD/non-POD — не важно) могут различаться.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, TepMuHyc, Вы писали:
TMH>По exception-safety все тоже прокатывает на ура — единственное место где может вылезти исключение — это конструктор копирования — в этом случае недоделанный временный экземпляр будет автоматически прибит а текущий экземпляр сохранит свое сотояние.
Намного безопаснее пользоваться идиомой обмена с временным объектом, а не побитовым копированием. Метод Swap() гарантировано не бросает исключений. Получаем строгую гарантию безопасности исключений:
Здравствуйте, VVV, Вы писали:
VVV>Видел подход наоборот — создаётся нормальный оператор присваивания, а конструктор копирования определяется через этот оператор (хотя, как я понимаю, тебе понравилась идея: дескруктор себе->конструктор себе->бинарная копия).
Вот почему мне не нравится такой подход: Конструктор создает обьект на пустом месте.
А оператор присваивания обязан корректно уничтожить предыдущее состояние обьекта и скопировать новое — что несколько сложнее.
Поэтому, конструктор копирования перед вызовом оператора присваивания обязан выполнить инициализацию обьекта — по сути дела вызвать конструктор по-умолчанию. Иначе оператор присваивания с треском навернется.
Налицо никому не нужная работа.
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Здравствуйте, 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 (но этого у тебя нет)
Что ты за бред городишь? Могу еще один дельный совет из разряда твоих идей насчет оператора присваивания дать, столь же мощное решение:
ты с конструктором копий не парься, а сразу пиши
*this = rhs;
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, TepMuHyc, Вы писали:
TMH>>Вот... Сотворил такой assignment.
IT>А зачем тогда вообще временная структура? Пиши прямо в сам класс
IT>
IT>my_class& operator=(const my_class& rhs)
IT>{
IT> new(this) my_class(rhs); //создаем новый временный экземпляр класса в буфере tmp.
IT> return *this;
IT>}
IT>
Если во время выполнения конструктора вылетает исключение то есть риск испортить данные предыдущего объекта и в результате получим исходный объект для которого вызывался оператор = в неопределенном состоянии( неясно какие данные в нем будут валидные, а какие нет ).
Здравствуйте, TepMuHyc, Вы писали: TMH> memcpy(this, &tmp, sizeof(*this));//бинарно копируем временный экземпляр в TMH>Скажу сразу: класс безопасен для бинарного копирования — т.е. он не содержит ссылок и указателей на самого себя. TMH>Значит, ДОЛЖНЫ быть грабли. Вопрос ГДЕ?
Недавно за пивом обсуждали эту тему...
Вся фишка в том, что приведение к типу "void*" это тяжкое наследие С, от которого надо избавляться. А в вызове memcpy() как раз это и происходит.
Если я правильно помню, никто не гарантирует, что размер ВСЕХ указателей между собой должен быть равен.
Посему, при такой операции приведения запросто можно потерять данные (на некоторой гипотетической машине, где динамическая память адресуется 64 битами, а статическая 16)
Уточните, если я не прав, но вроде, в стандарте это действительно не оговаривается...