Дык, вот я маленько в шоке от того, что меня активно стали убеждать, что именно так очень даже неплохо делать ... Быстро, просто, ничего лишнего...
Э ... интересно мнение студии.
И ещё интересно мнение на тему конструкции
X::X( X const & x )
{
// ... default initoperator =(x);
}
P.S.
Это не студенты. Вполне себе программисты со стажем. По сему задумался.
Здравствуйте, Alexey Chen, Вы писали:
AC>Сегодня рассказывал о реализации оператора копирования через swap, и по ходу в качестве примера привёл вариант как делать НЕ надо.
AC>
Изврат, хотя может у них там логика такая
AC>Дык, вот я маленько в шоке от того, что меня активно стали убеждать, что именно так очень даже неплохо делать ... Быстро, просто, ничего лишнего... AC>Э ... интересно мнение студии.
AC>И ещё интересно мнение на тему конструкции
AC>
AC>X::X( X const & x )
AC>{
AC> // ... default init
AC> operator =(x);
AC>}
AC>
если в связке с первым — Stack Overflow Exception)
я почти так делаю, тока тело первой конструкции другое)), без SOE)
В констр. копирования или в оп. присв. делаешь норм. инициализацию( почленно присваиваешь и .т.п.), а о оставшейся функции юзаешь первую
AC>P.S. AC>Это не студенты. Вполне себе программисты со стажем. По сему задумался.
Здравствуйте, Alexey Chen, Вы писали:
AC>Сегодня рассказывал о реализации оператора копирования через swap, и по ходу в качестве примера привёл вариант как делать НЕ надо.
AC>
тут полюбому надо проверка на присваивание самому себе, бывает редко, но бывает.
AC>
AC>X::X( X const & x )
AC>{
AC> // ... default init
AC> operator =(x);
AC>}
AC>
а это мне кажется параноя, хотя тут до хрипоты можно спорить, что раньше курица или яйцо.
вобще вариантов масса, можно например функцию CreateFrom сделать, но надо делать правильно, а именно реализовывать оператор присваивания посредством конструктора копирования, вобще говоря swap не всегда можно нормально реализовать, поэтому этот способ имеет право на существование.
B> вобще говоря swap не всегда можно нормально реализовать ...
Можно пример когда существует нетривиальный конструктор копирования и невозможно реализовать swap?
От себя добавлю: выглядит как хак, но иногда будет работать.
Пример, в котором упадет производительность: копирование чего-то вроде std::vector. При нормальной реализации оператора присваивания можно было бы ограничиться только копированием, если capacity позволяет, а тут получаем принудительное перевыделение.
AC>
AC>X::X( X const & x )
AC>{
AC> // ... default init
AC> operator =(x);
AC>}
AC>
Как и в первом случае, возможна потеря производительности. Сначала делаем (возможно долгую) default-инициализацию, возможно с выделением памяти и др. ресурсов, а затем выбрасываем результат этой работы на помойку (освобождаем память, что тоже требует времени, снова выделяем).
Здравствуйте, Alexey Chen, Вы писали:
B>> вобще говоря swap не всегда можно нормально реализовать ... AC>Можно пример когда существует нетривиальный конструктор копирования и невозможно реализовать swap?
Наверно имелось в виду, что обычно от swap требуют гарантии no throw.
У того же вектора swap — простое присваивание пары указателей, а конструктор копии вызывает выделение памяти, конструкторы копии всех элементов => куча возможностей получить исключение.
Так вот не для каждого класса swap — всего лишь присваивание пары указателей.
Здравствуйте, Alexey Chen, Вы писали:
AC>Можно пример когда существует нетривиальный конструктор копирования и невозможно реализовать swap?
swap нужен для того чтобы реализовать строгую гарантию безопасности исключений, тоесть если в конструкторе копирования произошло исключение, то после выполнения оператора присваивания наш объект остается неизмененным.
если класс определен
class A
{
.... // тут чето
};
class C
{
.... // тут чето
};
class B
{
private:
C c;
A a;
}
то вцелом swap реализовать нельзя, ибо мы не можем гаранитовать что swap(a, other.a) не сгенерит исключение. поэтому все наши труды идут под хвост.
это можно сделать если использовать А* вместо А и C* вместо C, что не всегда удобно.
например А распределяет динамически память для чегото, и не имее атомарной не генерящей исключений функции Swap std::bad_alloc можно поиметь всегда. В результате после выполнения swap имеем B.с от нового объекта, B.а от старого.
КЛ>class One
КЛ>{
КЛ>private:
КЛ> int i;
КЛ>public:
КЛ> One( const One& one )
КЛ> {
КЛ> *this = one;
КЛ> }
КЛ> One& operator=( const One& one )
КЛ> {
КЛ> i = one.i;
КЛ> return *this;
КЛ> }
КЛ>};
КЛ>
Re: оператор копирования
От:
Аноним
Дата:
07.09.05 16:10
Оценка:
Здравствуйте, Alexey Chen, Вы писали:
AC>Сегодня рассказывал о реализации оператора копирования через swap, и по ходу в качестве примера привёл вариант как делать НЕ надо.
AC>
AC>Дык, вот я маленько в шоке от того, что меня активно стали убеждать, что именно так очень даже неплохо делать ... Быстро, просто, ничего лишнего... AC>Э ... интересно мнение студии.
AC>И ещё интересно мнение на тему конструкции
AC>
AC>X::X( X const & x )
AC>{
AC> // ... default init
AC> operator =(x);
AC>}
AC>
AC>P.S. AC>Это не студенты. Вполне себе программисты со стажем. По сему задумался.
Вроде как по Стандарту- то, что выделенно:
return *(new (this) X(x));
Должно освобождаться через:
this->>~X();
Но в Вашем случае получается что:
X x1;
X x2(x1);
При выходе из области видимости будет вызван не inplace деструктор для x2, а нормальный деструктор, что противоречит
вышесказанному.
Здравствуйте, Alexey Chen, Вы писали:
AC>Дык, вот я маленько в шоке от того, что меня активно стали убеждать, что именно так очень даже неплохо делать ...
//чуть-чуть перепишу...
X& X::operator = ( X const &x )
{
this->~X();<-- обьекта удален, на его месте - мусор
X* tmp = new (this) X(x); <-- а здесь возникло исключение...
return *tmp;
}
В сухом остатке мы имеем то, что данный вариант оператора присваивания не выполняет Basic Exception Guarantee — то есть после исключения обьект остается в разрушенном состоянии. Что чревато, например, двойным вызовом деструктора. Впрочем, если гарантируется что конструктор копирования не бросатеся исключениями, то все будет хорошо. Вот только эту гарантию выполнить (и, главное, поддержать) не так легко как кажется.
Мое ХО — этот вариант присваивания масдай и бить за него надо крепко. Тк проблемы от такой "эффективности" могут начаться на ровном месте и в любой момент. Причем, их отлов и фикс выпьют немало крови.
AC>И ещё интересно мнение на тему конструкции
AC>X::X( X const & x )
Ничего не имею против безопасности такой конструкции, но она субэффективна. То есть, такой конструктор выполняет двоную работа: (а)инициализация (б)присваивание. Еще один довод против — надо удостовериться что операторы присваивания реализованы у всех дата мемберов этого класса, что, учитывая программистскую лень, может быть далеко так — например, для того чтобы позволить обьекту храниться в контейнере, программист делает ему CopyConstructor, а на оператор присваивания ложит болт тк в первом приближении он никому не нужен.
Итого, принимая во внимание простоту реализации member swap(), swap-trick в части создания операторов присваивания рулит немеряно. Я его применяю постоянно и вам того желаю.
__________
16.There is no cause so right that one cannot find a fool following it.
Вообще-то в операторе копирующего присваивания сначала удаляются текущие
ресурсы класса (т.к. присваиваем "живому" объекту, имеющему выделенные
ресурсы). Конструктор же оперирует с "мёртвым объектом", когда ресурсы
никакие не выделены! Поэтому правильно написанный оператор копирующего
присваивания попытается удалить ресурсы, которых у класса попросту нет!
> Вообще-то в операторе копирующего присваивания сначала удаляются текущие > ресурсы класса (т.к. присваиваем "живому" объекту, имеющему выделенные > ресурсы). Конструктор же оперирует с "мёртвым объектом", когда ресурсы > никакие не выделены! Поэтому правильно написанный оператор копирующего > присваивания попытается удалить ресурсы, которых у класса попросту нет!
У Страуструпа про это хорошо написано, раздел 10.4.4.1:
"Как это и бывает в большинстве случаев, копирующий конструктор и
копирующее присваивание значительно отличаются. Основная причина этого
состоит в том, что копирующий конструктор инициализирует "чистую"
(неинициализированную) память, в то время как копирующий оператор
присваивания должен правильно работать с уже созданным объектом."
(iliks: т.е. схема действий копирующего конструктора:
1. выделить новые ресурсы (память, ...)
2. скопировать члены переданного объекта.
схема действий оператора присваивания:
1. проверить, не присваиваем ли самому себе.
2. если нет, то удалить _текущее_внутреннее_представление_ (если оно
требует удаления или абстрактного "освобождения")
3. выделить новые ресурсы.
4. скопировать члены переданного объекта.
Как видим, в общем случае никак не возможно из копирующего конструктора
вызвать оператор присваивания, потому что семантика действий совсем разная.
плох тем, что не работатает если конструктор копирования бросит исключение. Также он не работает, если кто-то унаследован от X и деструктор виртуальный. Вот тут будет весело ^_^.
и этот трюк, и трюк со swap плох, тем, что assigment может быть выполнен эффективней cctor, так как не делает освобождение и выделение, скажем памяти.
Кто может сказать что-то плохое про
X::X( X const & x )
{
// ... default initoperator =(x);
}
?
За исключением, возможно, дешевых лишних действий.
Правильно работающая программа — просто частный случай Undefined Behavior
Здравствуйте, _Winnie, Вы писали:
_W>Подытожу.
_W>Трюк с _W>
this->>~X();
_W>return *(new (this) X(x));
_W>
_W>плох тем, что не работатает если конструктор копирования бросит исключение. Также он не работает, если кто-то унаследован от X и деструктор виртуальный. Вот тут будет весело ^_^.
+1
_W>Кто может сказать что-то плохое про
_W>
_W>X::X( X const & x )
_W>{
_W> // ... default init
_W> operator =(x);
_W>}
_W>
_W>? _W>За исключением, возможно, дешевых лишних действий.
КЛ>добавлю, и что дальше? Часто конструктор копирования и оператор присваивания имеют одну и ту же логику, и один реализуется через другой
One& operator=( const One& one )
{
i = one.i;
canThrow1 = one.canThrow1;
canThrow2 = one.canThrow2;
}
С твоим конструктором копирования, реализованным посредством оператора присваивания все будет в порядке, тоесть будет брошено исключение, и объект не будет создан.
С оператором присваивания все хуже, ведь он вызывается для уже существующего объекта, и если произойдет исключение при вызове оператора= для canThrow1 или canThrow1, то объект класса One останется в некорректном состоянии
При обратной реализации(c временным объектом) этого не произойдет.