Здравствуйте, Artifact, Вы писали:
A>Разве кто-то в здравом уме станет присваивать что-то самому себе? И, если уж так вышло, не является ли это ошибкой? И не лучше ли тогда вместо A>
MyClass& operator=(const MyClass& other) {
A> if(this != &other) {
A> * * *
A> }
A>}
A>писать A>
MyClass& operator=(const MyClass& other) {
A> assert(this != &other)
A> * * *
A>}
Здравствуйте, Artifact, Вы писали:
A>Разве кто-то в здравом уме станет присваивать что-то самому себе? И, если уж так вышло, не является ли это ошибкой? И не лучше ли тогда вместо A>
MyClass& operator=(const MyClass& other) {
A> if(this != &other) {
A> * * *
A> }
A>}
A>писать A>
MyClass& operator=(const MyClass& other) {
A> assert(this != &other)
A> * * *
A>}
А потом вдруг окажется, что при некотором хитром сценарии работы Большого Алгоритма прасваивание самому себе все-таки возможно. И тогда придется ставить первый вариант использования, потому что вносить изменения в Большой Алгоритм себе дороже.
Собственно говоря, первый вариант позволяет нам расслабиться. Встретится присваивание самому себе раз в год, два раза на пасху. Ну и черт с ним. Во втором варианте, чем сложнее логика операций вокруг MyClass, тем больше шансов, что ружье выстрелит.
Re: Зачем проверять на неравенство this в операторе присваивания?
Здравствуйте, Artifact, Вы писали:
A>Разве кто-то в здравом уме станет присваивать что-то самому себе? И, если уж так вышло, не является ли это ошибкой? И не лучше ли тогда вместо
Писать "x = x", конечно, никто в здравом уме не будет.
В вот "x = y", где x, например, член класса, а y — переданный в его метод параметр — запросто.
Сам сталкивался с ситуацией, когда они случайно ссылались на один и тот же объект, а проверку вставить забыли.
Отловить быг было довольно тяжело.
Re[4]: Зачем проверять на неравенство this в операторе присваивания?
Здравствуйте, bydlocoder, Вы писали:
B>Здравствуйте, Andrew S, Вы писали:
AS>>За такое надо ... даже не могу придумать что сделать. Ну, Саттера заставить перечитать 20 раз, что ли.
B>"Те не понял, защитник братьев меньших" (с) Jay and Silent Bob B>Я не призываю так писать. B>Вопрос ТС был в том, кто в здравом уме будет присваивать себе.
B>Я привел вполне жизненный юзкейс, а также пример, когда он приведет к очень-очень плохим последствиям
B>>>
B>>>X& operator=(const X& rhs){
B>>> clear_me(); // Эвона как. А если сконструировать не получится???
B>>>
B>Спасибо, Капитан!
AS>>Лучше не писать овнокод. B>Это все хорошо, пока живешь в мире розовых пони, которые кушают радугу и какают бабочками. B>Если возраст codebase около 20 лет, а размер — несколько сотен мегабайт, случай, когда исправляешь memleak, а на нем строится некая логика в соседней либе — это не страшный сон, а реальная жизнь
Вопрос был про то, зачем сравнивать. Ваш ответ, соответственно, не в кассу (более того, приведенный код просто неработоспособен). Правильный ответ — в абсолютном большинстве случаев сравнение с this не нужно. Только в очень редких кейзах, где необходимость оптимизации доказана профилированием, можно написать что-то более умное, чем типовая реализация.
AS>>>X & operator = (X rhs)
AS>>>{
AS>>> using namespace std; // а вот за такое... да два-три раза...
AS>>> swap(*this, rhs); // Без перегрузки перемещающего к-тора (или swap) оно, по ходу, и в C++11 уйдет в рекурсию
AS>>> return *this;
AS>>>}
AS>>>
А какие ко мне претензии? В том посте, на который отвечал я, swap уже был. Соответственно, либо он перегружен, либо у того, кто писал тот код, ну совсем каша. Прочитайте рекомендованную литературу, право.
Здравствуйте, Andrew S, Вы писали:
AS>Вопрос был про то, зачем сравнивать.
Ответ тоже был, зачем сравнивать.
AS>Ваш ответ, соответственно, не в кассу (более того, приведенный код просто неработоспособен).
В каком месте, позвольте полюбопытствовать? Вполне работоспособен. Могу пояснить.
В операторе присвоения сначала производится очистка, потом создается объект из переданного по ссылке (который уже очищен), и производится swap.
Само собой, это поведение неправильное.
Но представьте, что неправильно оно никогда не использовалось. И тут в вашей библиотеке, которая линкуется с приведенным выше кодом, написанным Раджой и Кумаром в 1995 году, используется этот объект в качестве, например, члена класса. Мне продолжать?..
AS>Правильный ответ — в абсолютном большинстве случаев сравнение с this не нужно.
Я и не говорю про абсолютно большинство. Я говорю про абсолютное меньшинство, которое позволяет метко выстрелить себе в ногу.
Re[6]: Зачем проверять на неравенство this в операторе присваивания?
AS>>Вопрос был про то, зачем сравнивать. B>Ответ тоже был, зачем сравнивать.
AS>>Ваш ответ, соответственно, не в кассу (более того, приведенный код просто неработоспособен). B>В каком месте, позвольте полюбопытствовать? Вполне работоспособен. Могу пояснить. B>В операторе присвоения сначала производится очистка, потом создается объект из переданного по ссылке (который уже очищен), и производится swap. B>Само собой, это поведение неправильное. B>Но представьте, что неправильно оно никогда не использовалось. И тут в вашей библиотеке, которая линкуется с приведенным выше кодом, написанным Раджой и Кумаром в 1995 году, используется этот объект в качестве, например, члена класса. Мне продолжать?..
Неужели не очевидно, что этот код просто неработоспособен?
Итак, разберем 2 возможности
1. swap перегружен на X
В этом случае swap не падает, т.е. это противоречит комментарию. Значит, swap не перегружен. В целом, вообще не понятно, зачем в этом коде нужен вызов clear_me, если есть копирование во временный объект.
2. стандартная реализация swap.
Код неработоспособен вне зависимости от наличия или отсутствия проверки на this, просто из-за потенциальной бесконечной рекурсии (приведенная к условиям примера стандартная реализация swap):
(на самом деле, этот код может не падать, если есть move копирование, но это, очевидно, не наш случай ввиду наличия комментария.
Т.о., в продакшен (да и вообще в любом) коде этого примера быть не могло, необходимость проверки на this он не показывает. Зачем он?
AS>>Правильный ответ — в абсолютном большинстве случаев сравнение с this не нужно. B>Я и не говорю про абсолютно большинство. Я говорю про абсолютное меньшинство, которое позволяет метко выстрелить себе в ногу.
Вас спрашивают не почему, а зачем. Ответа пока не было. Был невнятный и неработоспособный пример и попытки его обосновать. Садитесь, два.
AS>Итак, разберем 2 возможности AS>1. swap перегружен на X
Разумеется, иначе я бы указал префикс std.
Если вам так больше нравится, пусть будет
this->swap(tmp);
В контексте примера это абсолютно не важно.
AS>В этом случае swap не падает, т.е. это противоречит комментарию. Значит, swap не перегружен. В целом, вообще не понятно, зачем в этом коде нужен вызов clear_me, если есть копирование во временный объект.
Вы кроме себя, кого-то умеете читать? Я привет пример legacy-code, к которорому мы в общем случае можем не иметь доступа, например, мы используем объект из сторонней библиотеки или реализуем вокруг него какой-нибудь фасад.
Перед присвоением объект может быть очищен? Разумеется, может. Так захотелось разработчику.
Что произойдет после присвоения неконсистентного объекта, можно только гадать. Может, свалится сразу, может, при его использовании.
Вы упорно говорите, что проверка на this не нужна по причине производительности, когда как производительность — последняя причина ее реализовывать.
AS>Вас спрашивают не почему, а зачем. Ответа пока не было. Был невнятный и неработоспособный пример и попытки его обосновать. Садитесь, два.
Я думаю, если вышеприведенные примеры вас не убедили, ваша квалификация недостаточна, чтобы меня оценивать
Re[5]: Зачем проверять на неравенство this в операторе присваивания?
Здравствуйте, Artifact, Вы писали:
A>Разве кто-то в здравом уме станет присваивать что-то самому себе? И, если уж так вышло, не является ли это ошибкой? И не лучше ли тогда вместо
Раз уж такой пустяковый вопрос вызвал настоящий le bougourte, думаю, есть смысл в более развернутом примере.
#include <iostream>
// Представьте, что доступа к этопу коду мы не имеем. Физически, организационно
// или просто люди, которые его разрабатывали или поддерживали, давно отсуствуют.
// На самом деле, это не сильно отличается от первого предположенияclass LegacyObj
{
public:
LegacyObj(int i):_i(new int(i)){}
~LegacyObj(){
clear();
}
LegacyObj(const LegacyObj& rhs){
clear();
_i = new int(*rhs._i);
}
LegacyObj& operator=(const LegacyObj& rhs){
// этот вызов явно лишний, но ничего с ним сделать не можем
clear();
// копируем наши "ресурсы"
_i = new int(*rhs._i);
// настоящие ковбои никогда не проверяют &rhs != thisreturn *this;
}
void clear(){
delete _i;
// _i = 0; - если бы еще обнулили этот указатель, это нам бы помогло при отладке
}
void print_me(){
std::cout << *_i << std::endl;
}
private:
// вместо int* могут быть любые ресурсы, требующие аллокацииint* _i;
};
// Какая-то обертка вокруг старого кода
// Версия с проверкойclass WrapperWithCheck
{
public:
WrapperWithCheck(int a, int b, int c):_a(a),_b(b),_c(c){}
WrapperWithCheck(const WrapperWithCheck& rhs):_a(rhs._a),_b(rhs._b),_c(rhs._c){}
WrapperWithCheck& operator=(const WrapperWithCheck& rhs){
if(&rhs == this)
return *this;
_a = rhs._a;
_b = rhs._b;
_c = rhs._c;
return *this;
}
void print_me(){
_a.print_me();
_b.print_me();
_c.print_me();
}
private:
LegacyObj _a;
LegacyObj _b;
LegacyObj _c;
};
// Версия без проверкиclass WrapperWithoutCheck
{
public:
WrapperWithoutCheck(int a, int b, int c):_a(a),_b(b),_c(c){}
WrapperWithoutCheck(const WrapperWithoutCheck& rhs):_a(rhs._a),_b(rhs._b),_c(rhs._c){}
WrapperWithoutCheck& operator=(const WrapperWithoutCheck& rhs){
_a = rhs._a;
_b = rhs._b;
_c = rhs._c;
return *this;
}
void print_me(){
_a.print_me();
_b.print_me();
_c.print_me();
}
private:
LegacyObj _a;
LegacyObj _b;
LegacyObj _c;
};
int main() {
{
WrapperWithCheck wrapper_with_check(1, 2, 3);
WrapperWithCheck other_wrapper(5, 6, 7);
std::cout << "Object with check" << std::endl;
wrapper_with_check.print_me();
WrapperWithCheck& wrapper_ref = wrapper_with_check;
// присваивание другому объекту
other_wrapper = wrapper_with_check;
// присваивание себе
wrapper_with_check = wrapper_ref;
std::cout << "After assignment" << std::endl;
wrapper_with_check.print_me();
}
{
WrapperWithoutCheck wrapper_without_check(1, 2, 3);
WrapperWithoutCheck other_wrapper(5, 6, 7);
std::cout << "Object without check" << std::endl;
wrapper_without_check.print_me();
WrapperWithoutCheck& wrapper_ref = wrapper_without_check;
// присваивание другому объекту
other_wrapper = wrapper_without_check;
// присваиваем себе
wrapper_without_check = wrapper_ref;
std::cout << "After assignment" << std::endl;
// вывод здесь не определен
// если бы обнулили указатель в clear() - получили бы debug assert и release crash
wrapper_without_check.print_me();
}
return 0;
}
VC++ 12.0
Object with check
1
2
3
After assignment
1
2
3
Object without check
1
2
3
After assignment
-842150451
-842150451
-842150451
Так что отвечая на вопрос "нужна ли проверка на присваивание самому себе", ответ — "иногда нужна"
Когда она нужна — это другой вопрос. Я бы сказал, когда мы присваиваем нетривиальные объекты. Насколько нетривиальные — надо отталкиваться от common sense approach. Нам, в конце концов, за это платят деньги.
Зачем она нужна — конечно, в последнюю очередь из соображений производительности. Как показывает пример выше (несколько надуманный, но на то он и пример), она нужна для повышения общей надежности и стабильности, особенно в крупных системах с десятками и сотнями разработчиков и сотнями мегабайт исходников.
И пожалуйста, не стоит всерьез воспринимать фразы типа "проверка на this никогда не нужна". Они происходят из категории "исключения не нужны", "писать нужно только в vim" и "Lisp — лучший язык программирования". Они выдают в вас юношу бледного со взором горящим.
Профессионал же в состоянии понять, что на многие сложные вопросы единственный правильный ответ отсутствует.
Re[8]: Зачем проверять на неравенство this в операторе присваивания?
AS>>Итак, разберем 2 возможности AS>>1. swap перегружен на X B>Разумеется, иначе я бы указал префикс std. B>Если вам так больше нравится, пусть будет B>
this->>swap(tmp);
B>
Тогда swap падать не будет, это противоречит комментарию. Мимо.
B>В контексте примера это абсолютно не важно.
AS>>В этом случае swap не падает, т.е. это противоречит комментарию. Значит, swap не перегружен. В целом, вообще не понятно, зачем в этом коде нужен вызов clear_me, если есть копирование во временный объект. B>Вы кроме себя, кого-то умеете читать? Я привет пример legacy-code, к которорому мы в общем случае можем не иметь доступа, например, мы используем объект из сторонней библиотеки или реализуем вокруг него какой-нибудь фасад. B>Перед присвоением объект может быть очищен? Разумеется, может. Так захотелось разработчику.
Не может. Если есть clear_me, то никакого конструирования во временный объект и swap не будет. Будет вызов copy_from. Что вы и продемонстрировали своим примером в другом сообщении.
B>Что произойдет после присвоения неконсистентного объекта, можно только гадать. Может, свалится сразу, может, при его использовании. B>Вы упорно говорите, что проверка на this не нужна по причине производительности, когда как производительность — последняя причина ее реализовывать.
Такого сказано не было. Читайте внимательнее.
AS>>Вас спрашивают не почему, а зачем. Ответа пока не было. Был невнятный и неработоспособный пример и попытки его обосновать. Садитесь, два. B>Я думаю, если вышеприведенные примеры вас не убедили, ваша квалификация недостаточна, чтобы меня оценивать
Безусловно, моя квалификация как оценивателя овнокода крайне низка. Я его всегда оцениваю крайне низко
PS Я вот не понимаю, зачем вы изворачиваетесь. Ну написали фигню, бывает. Потом ведь сами привели более корректный пример:
LegacyObj& operator=(const LegacyObj& rhs){
// этот вызов явно лишний, но ничего с ним сделать не можем// этот вызов явно лишний, но ничего с ним сделать не можем
clear();
// копируем наши "ресурсы"// копируем наши "ресурсы"
_i = new int(*rhs._i);
// настоящие ковбои никогда не проверяют &rhs != this// настоящие ковбои никогда не проверяют &rhs != thisreturn *this;
}
Разница с приведенным ранее очевидна. Это код хотя бы потенциально работоспособен.
P>// swap должен быть перегружен для X и не кидать исключения. Иначе получим рекурсию. А если перегружен — то не получим ошибку, за исключением выше.
P>Не догоняю, зачем здесь using?
Обычный lifehack.
Внешня перегрузка swap для своего объекта (возможно, с использованием мембера swap) пишется только в том случае, если не устраивает стандартная. А стандартная (в С++11) теперь стала не устраивать сильно реже. В результате код одинаково работоспособен в любом из кейзов: как при наличии мембера swap, внешней функции swap в неймспейсе типа, перегрузки std::swap, стандартной реализации std::swap, причем всегда автоматически выбирается наиболее специализированный вариант реализации.
Здравствуйте, Andrew S, Вы писали:
AS>Не может. Если есть clear_me, то никакого конструирования во временный объект и swap не будет. Будет вызов copy_from. Что вы и продемонстрировали своим примером в другом сообщении.
Вы, похоже, ищете повод, чтобы придраться к примеру. Можно было еще сказать, что без функции main() не скомпилируется.
Совершенно очевидно, это пример не про swap, он был приведен не как пример кода для компиляции, а как концепция.
B>>Вы упорно говорите, что проверка на this не нужна по причине производительности, когда как производительность — последняя причина ее реализовывать. AS>Такого сказано не было. Читайте внимательнее.
Правильный ответ — в абсолютном большинстве случаев сравнение с this не нужно. Только в очень редких кейзах, где необходимость оптимизации доказана профилированием, можно написать что-то более умное, чем типовая реализация.
AS>PS Я вот не понимаю, зачем вы изворачиваетесь. Ну написали фигню, бывает. Потом ведь сами привели более корректный пример
Абсолютно верно, корректный пример был приведен для компиляции, а "фигня" была для выражения концепции. Я бы мог еще написать /* а вот здесь скопируем ресурс */, и пример бы от этого не пострадал.
Если бы вы не поняли смысл исходного примера и переспросили, я бы его пояснил, и мы бы остались довольны друг другом. Но вы предпочли скатиться в хамский и менторский тон, а у меня в таких случаях пропадает желание что-либо обсуждать.
Вы хотели продемонстировать знания, а переходом на личности продемонстрировали только недостойное поведение. Некрасиво, право же
Re[10]: Зачем проверять на неравенство this в операторе присваивания?
AS>>Не может. Если есть clear_me, то никакого конструирования во временный объект и swap не будет. Будет вызов copy_from. Что вы и продемонстрировали своим примером в другом сообщении. B>Вы, похоже, ищете повод, чтобы придраться к примеру. Можно было еще сказать, что без функции main() не скомпилируется. B>Совершенно очевидно, это пример не про swap, он был приведен не как пример кода для компиляции, а как концепция.
Претензия была не к компилируемости, а к концепции. Там, где концепция была корректна (см выше), претензий не было.
B>>>Вы упорно говорите, что проверка на this не нужна по причине производительности, когда как производительность — последняя причина ее реализовывать. AS>>Такого сказано не было. Читайте внимательнее. B>
B>Правильный ответ — в абсолютном большинстве случаев сравнение с this не нужно. Только в очень редких кейзах, где необходимость оптимизации доказана профилированием, можно написать что-то более умное, чем типовая реализация.
И? Где здесь говориться про то, что проверка на this противоречит производительности?
AS>>PS Я вот не понимаю, зачем вы изворачиваетесь. Ну написали фигню, бывает. Потом ведь сами привели более корректный пример
B>Абсолютно верно, корректный пример был приведен для компиляции, а "фигня" была для выражения концепции. Я бы мог еще написать /* а вот здесь скопируем ресурс */, и пример бы от этого не пострадал. B>Если бы вы не поняли смысл исходного примера и переспросили, я бы его пояснил, и мы бы остались довольны друг другом. Но вы предпочли скатиться в хамский и менторский тон, а у меня в таких случаях пропадает желание что-либо обсуждать.
Вы привели некорректный пример и не можете этого признать. Это объективно. Я только это озвучил — ко мне то какие вопросы?
B>Вы хотели продемонстировать знания, а переходом на личности продемонстрировали только недостойное поведение. Некрасиво, право же
Детский сад, штаны на лямках. Учитесь признавать собственные ошибки.
Здравствуйте, Andrew S, Вы писали:
AS>>>Не может. Если есть clear_me, то никакого конструирования во временный объект и swap не будет. Будет вызов copy_from. Что вы и продемонстрировали своим примером в другом сообщении. B>>Вы, похоже, ищете повод, чтобы придраться к примеру. Можно было еще сказать, что без функции main() не скомпилируется. B>>Совершенно очевидно, это пример не про swap, он был приведен не как пример кода для компиляции, а как концепция.
AS>Претензия была не к компилируемости, а к концепции. Там, где концепция была корректна (см выше), претензий не было.
AS>Вы привели некорректный пример и не можете этого признать. Это объективно.
Да, первый пример некорректен. Вам легче? Но вы смоими комментариями продемонстрировали (а) его непонимание и (б) неприкрытое хамство. Это не менее объективно.
AS>Я только это озвучил — ко мне то какие вопросы?
К вам не может быть никаких вопросов, ваш уровень понятен. Засим прекращаю эту контрпродуктивную дискуссию.
Re[7]: Зачем проверять на неравенство this в операторе присваивания?
Здравствуйте, Andrew S, Вы писали:
P>>Не догоняю, зачем здесь using?
AS>Обычный lifehack. AS>В результате код одинаково работоспособен в любом из кейзов: как при наличии мембера swap, внешней функции swap в неймспейсе типа, перегрузки std::swap, стандартной реализации std::swap, причем всегда автоматически выбирается наиболее специализированный вариант реализации.
Дык
// swap должен быть перегружен для X и не кидать исключения. Иначе получим рекурсию. А если перегружен — то не получим ошибку, за исключением выше.
же!!111
Почетный кавалер ордена Совка.
Re[8]: Зачем проверять на неравенство this в операторе присваивания?
P>>>Не догоняю, зачем здесь using?
AS>>Обычный lifehack. AS>>В результате код одинаково работоспособен в любом из кейзов: как при наличии мембера swap, внешней функции swap в неймспейсе типа, перегрузки std::swap, стандартной реализации std::swap, причем всегда автоматически выбирается наиболее специализированный вариант реализации.
P>Дык P>
P>// swap должен быть перегружен для X и не кидать исключения. Иначе получим рекурсию. А если перегружен — то не получим ошибку, за исключением выше.
P>же!!111
Если речь все про зачем using — чтобы не задумываться о том, откуда брать реализацию, за нас это решит ADL.
Ровно так же корректно, если в классе есть нестатический мембер swap, было бы написать swap(other), но в оригинальном варианте был вызов внешней (ну или статической) функции swap, что я и оставил. В целом, это скорее просто моя личная привычка давать возможность не перегружать стандарный swap, а написать свой в неймспейсе класса — не всегда имеется возможность или желание сделать такую перегрузку. В реализации оператора = оно, наверное, особо смысла не несет, по крайней мере, сильно меньший, чем просто в клиенском коде.