А теперь позвольте мне вам разложить все по пунктам.
Если вы применяете синглтон, то обычно это многопоточный код, потому что я не очень понимаю, как вы ограничите создание вашего класса в пределах одного потока.
Я могу предположить, что внутри себя синглтон использует thread_local, но это как минимум странно, а также сильно усложняет понимание кода и его тестирование.
Для однопоточного случая обычно проще передать ссылку в конструкторе - это куда читабельнее.
Далее, обозреватель - в принципе плохой паттерн. Он разрывает локальность графа выполнения и затрудняет понимание. Я вдвойне не понимаю, зачем он нужен в однопоточном коде.
Вы так кичитесь своим знанием языка, что забыли спросить, какого типа здесь object и какова семантика перемещения его класса. Вот так одна строчка кода оставила в дураках большого знатока с большим апломбом. А ведь такое может попасться в реальном коде, и у меня, в отличие от вас, выработалась паранойя досконально проверять все плохо пахнущее.
Потом: я специально придумал случай с 1000 строками кода, потому что я именно ждал от вас и таких, как вы, аппеляций к личности, каких-то там ярлыков кривокодера и прочего. Вы даже не в состоянии понять, что в любом количестве строк кода, которые вы считаете приемлемым, эта ошибка никуда не денется.
Более того, как я уже аргументировал, эта ошибка - артефакт языка, ваши и подобные примеры очень легко перенести на любой другой язык, и они там будут столь же некорректны. А мой - применим только к C++.
Ок, для каких-то случаев это может быть существенно.
Здесь вы даже не в состоянии понять, что моя реплика равносильна: "существуют случаи, для которых это существенно" в логике первого порядка.
По поводу моего "говнокода": его едининственный недостаток - он не скомпилируется при отсутствии дефолтного конструктора у класса или одного из его членов. Но меня это не волнует.
Далее я привожу вам тот самый пример, когда это действительно существенно, и делаю оговорку, что меня это не волнует.
При этом вы очень прыткий и хорошо владеете языком, но не понимаете, как его применять. На таких, как вы, я насмотрелся на собеседованиях: обычно это вчерашние студенты, вызубрившие "банду четырех" и "свой любимый C++", и выливающие все эти знания на специалиста в надежде показаться крутыми.
Именно поэтому вы не в состоянии читать чужие мысли в тексте - вы просто к этому еще не готовы. Но при этом вы снова-таки легко перекладываете свое непонимание на других, еще и в достаточно грубой форме.
Я надеюсь, вы прочтете это и сначала задумаетесь, прежде чем выпаливать очередной несуразный комментарий.
Кстати, ещё два вопроса к вашему примеру: вы правда считаете синглтон и кардинально различное поведение конструктора копирования и оператора = хорошим стилем?
Удачи в многопоточной реализации вашего подхода! Будете иметь иногда два сообщения, а также дергать мьютекс без надобности на каждом копировании. Контекст важен.
По поводу моего "говнокода": его едининственный недостаток - он не скомпилируется при отсутствии дефолтного конструктора у класса или одного из его членов. Но меня это не волнует. Остальную неэффективность выпилит оптимизатор. На случай возражений о тяжёлых дефолтных конструкторах - так это вот говнокод, не делайте так.
А ещё потрудитесь понимать, что вам пишут. Я все время замечаю, что вы от части комментируете что-то вам непонятное, но это проблема может быть с обеих сторон.
Вы прекрасно владеете инструментом, но явно не понимаете, что инструмент - это не все. И это как раз был один из посылов статьи. Хотите быть синим воротничком - ваше право.
Почему вы думаете, что наличие отдельного operator= как-то связано с цепочечным присваиванием?
Если это не так - просветите, в чем необходимость двух сильно похожих методов?
Это говнокод. За такое по рукам нужно бить.
И в чем его реальная проблема, помимо вкусовщины?
И тут в дело вступает exception safety. Как минимум
То есть в операторе = какая-то особая exception safety? В чем принципиальная разница? В стандарте, кажется, исключение на неполностью сконструированном объекте вполне себе определено. Из этого следует, что если бы такой конструктор вызывался в месте присваивания, то объект был уже сконструирован ранее, поэтому правило выше не применялось. Но это надо было делать так сразу.
Проблема в том, что имеем по два члена-метода (а всего 4), делающих почти всегда одно и то же. Я встречал плохой код, где забывали что-то из этого или принципиально не добавляли, потому что логика ломалась.
Мне непонятно, зачем нужно два метода на одно и то же. Достаточно только конструктора, если перестать поддерживать цепочечное присваивание. На каждое обычное присваивание достаточно вызавать конструктор. Проблем с этим я не вижу.
Вспомнил ещё из любимого, когда смотрел в код: дублирование конструкторов копирования/перемещения и операторов =. Логика в них зачастую одинаковая, поэтому придумали несколько трюков, чтоб избегать дублирования. Но проблема-то простая: оператор = перегружен смыслом. Помимо копирования/перемещения, он он был спроектирован для поддержки редкого синтаксиса цепочечного присваивания a = b = c. В итоге имеем дополнительную проблему на ровном месте.
Для многих инженеров выбор C или C++ десятилетиями был предопределен родом деятельности. Я не от большой любви на них пишу, но очень долго альтернатив-то не было. Думаю, со временем очень многие из них придут к Rust. Я уже видел, что в некоторых научных статьях начали давать примеры кода на Rust.
Вообще, плюсы не учат хорошему вкусу в программировании. Возможно, кстати, для людей с наукоемким софтом это и не так важно: у них задача другая. Но вот когда кода реально много - тут ведь без вариантов.
Многим людям, таким как я, наконец-то дали инструмент, на котором можно творить в потоке: лить код из головы и не отвлекаться на шероховатости языка, перепроверяя себя каждую секунду. С Rust я впервые почувствовал, что такое быть "высокоуровневым" программистом. Это очень крутое и вдохновляющее ощущение. Думаю, за фанатизмом любителей Rust скрывается благодарность за подобную отдушину.
звездочка отдельно от типа указателя при объявлении: int *p, *t;
присваивания в операторах
неявные преобразования типов
В плюсах:
порядок объявления членов vs порядок их указания в конструкторе
виртуальное наследование
конструкторы: A(const A&&) {} - разрешено, но зачем; A(A&) {} - пропущен const - и это уже не копирующий конструктор (увы, как-то видел такое в реальном коде, пришлось долго искать причину поломки). сам принцип того, что при объявлении конструктора все может взорваться где-то в другом месте, потому что конструктор по умолчанию больше не генерируется
lvalue, rvalue, prvalue, xvalue (ничего не пропустил?)
Я понял правильную аргументацию: возьмите ваш пример с некорректной очисткой вектора и перенесите его в любой другой язык программирования. Ошибка кодирования сохранится, алгоритм будет работать некорректно. А теперь попробуйте сделать то же самое с предложенным мной ниже примером с перемещением.
В этом и есть разница: семантика перемещения - это артефакт отдельного языка, служащий "костылем" для повышения производительности, который сам по себе был рождён в результате неверно принятых концептуальных решений предыдущих этапов.
Думал над вашим ответом в свободное время. Могу его представить, но не могу понять, как из него и из основного доказательства с необходимостью следует теорема Пифагора. Ваше интуитивное понимание на другом уровне.
Обычно системное ПО определяется тем, что оно напрямую взаимодействует с операционной системой и/или с устройствами. Также в его задачи входит самостоятельный учет и контроль ресурсов системы во время выполнения. То есть, в отличие от прикладного ПО, здесь меньше слоев абстракции между кодом и непосредственно железом.
Спасибо! Длинное видео, попробую осилить в свободное время хотя бы на перемотке. Понимаете, на конкретные мелочи я насмотрелся достаточно, но вот прийти к некому обобщению, которое было бы понятным лично мне, долго не удавалось. Эта статья - и есть такое обобщение.
Отличается принципиально: в подобном коде всегда есть явное намерение автора для достижения некоторой поставленной задачи его алгоритма. Если это нетривиальный ход - желательно его откомментировать. Но я всегда могу сделать git blame и спросить у автора строки его понимание вещей напрямую.
Перемещение 9-го по счету параметра в вызываемый метод - это не более чем артефакт языка, который не несет никакой полезной нагрузки. Более того, его очень просто не заметить.
Я тоже знаю, как должно быть правильно но я все еще не живу в идеальном мире. Разработка за деньги обычно предполагает сроки, взаимодействие с коллегами и это вот все. Увы.
Спасибо, запомню.
А теперь позвольте мне вам разложить все по пунктам.
Если вы применяете синглтон, то обычно это многопоточный код, потому что я не очень понимаю, как вы ограничите создание вашего класса в пределах одного потока.
Я могу предположить, что внутри себя синглтон использует thread_local, но это как минимум странно, а также сильно усложняет понимание кода и его тестирование.
Для однопоточного случая обычно проще передать ссылку в конструкторе - это куда читабельнее.
Далее, обозреватель - в принципе плохой паттерн. Он разрывает локальность графа выполнения и затрудняет понимание. Я вдвойне не понимаю, зачем он нужен в однопоточном коде.
Вернемся теперь к этому:
some_method(...., ...., ..., ...., ...., std::move(object), ..., ...., ..., );иsome_method(...., ...., ..., ...., ...., object, ..., ...., ..., );Вы так кичитесь своим знанием языка, что забыли спросить, какого типа здесь object и какова семантика перемещения его класса. Вот так одна строчка кода оставила в дураках большого знатока с большим апломбом. А ведь такое может попасться в реальном коде, и у меня, в отличие от вас, выработалась паранойя досконально проверять все плохо пахнущее.
Потом: я специально придумал случай с 1000 строками кода, потому что я именно ждал от вас и таких, как вы, аппеляций к личности, каких-то там ярлыков кривокодера и прочего. Вы даже не в состоянии понять, что в любом количестве строк кода, которые вы считаете приемлемым, эта ошибка никуда не денется.
Более того, как я уже аргументировал, эта ошибка - артефакт языка, ваши и подобные примеры очень легко перенести на любой другой язык, и они там будут столь же некорректны. А мой - применим только к C++.
Здесь вы даже не в состоянии понять, что моя реплика равносильна: "существуют случаи, для которых это существенно" в логике первого порядка.
Далее я привожу вам тот самый пример, когда это действительно существенно, и делаю оговорку, что меня это не волнует.
При этом вы очень прыткий и хорошо владеете языком, но не понимаете, как его применять. На таких, как вы, я насмотрелся на собеседованиях: обычно это вчерашние студенты, вызубрившие "банду четырех" и "свой любимый C++", и выливающие все эти знания на специалиста в надежде показаться крутыми.
Именно поэтому вы не в состоянии читать чужие мысли в тексте - вы просто к этому еще не готовы. Но при этом вы снова-таки легко перекладываете свое непонимание на других, еще и в достаточно грубой форме.
Я надеюсь, вы прочтете это и сначала задумаетесь, прежде чем выпаливать очередной несуразный комментарий.
Всего доброго! Живите счастливо!
Кстати, ещё два вопроса к вашему примеру: вы правда считаете синглтон и кардинально различное поведение конструктора копирования и оператора = хорошим стилем?
Уверен.
Удачи в многопоточной реализации вашего подхода! Будете иметь иногда два сообщения, а также дергать мьютекс без надобности на каждом копировании. Контекст важен.
По поводу моего "говнокода": его едининственный недостаток - он не скомпилируется при отсутствии дефолтного конструктора у класса или одного из его членов. Но меня это не волнует. Остальную неэффективность выпилит оптимизатор. На случай возражений о тяжёлых дефолтных конструкторах - так это вот говнокод, не делайте так.
А ещё потрудитесь понимать, что вам пишут. Я все время замечаю, что вы от части комментируете что-то вам непонятное, но это проблема может быть с обеих сторон.
Вы прекрасно владеете инструментом, но явно не понимаете, что инструмент - это не все. И это как раз был один из посылов статьи. Хотите быть синим воротничком - ваше право.
Конструкторы не возвращают значений, а оператор = возвращает ссылку на себя. Насколько я знаю, это нужно только для цепочечного присваивания.
Ок, для каких-то случаев это может быть существенно.
Давайте тут закончим, вы меня просветили достаточно.
Если это не так - просветите, в чем необходимость двух сильно похожих методов?
И в чем его реальная проблема, помимо вкусовщины?
То есть в операторе = какая-то особая exception safety? В чем принципиальная разница? В стандарте, кажется, исключение на неполностью сконструированном объекте вполне себе определено. Из этого следует, что если бы такой конструктор вызывался в месте присваивания, то объект был уже сконструирован ранее, поэтому правило выше не применялось. Но это надо было делать так сразу.
A(const A& a) { *this = a; }
И вся логика в операторе присваивания.
Проблема в том, что имеем по два члена-метода (а всего 4), делающих почти всегда одно и то же. Я встречал плохой код, где забывали что-то из этого или принципиально не добавляли, потому что логика ломалась.
Мне непонятно, зачем нужно два метода на одно и то же. Достаточно только конструктора, если перестать поддерживать цепочечное присваивание. На каждое обычное присваивание достаточно вызавать конструктор. Проблем с этим я не вижу.
Вспомнил ещё из любимого, когда смотрел в код: дублирование конструкторов копирования/перемещения и операторов =. Логика в них зачастую одинаковая, поэтому придумали несколько трюков, чтоб избегать дублирования. Но проблема-то простая: оператор = перегружен смыслом. Помимо копирования/перемещения, он он был спроектирован для поддержки редкого синтаксиса цепочечного присваивания a = b = c. В итоге имеем дополнительную проблему на ровном месте.
Для многих инженеров выбор C или C++ десятилетиями был предопределен родом деятельности. Я не от большой любви на них пишу, но очень долго альтернатив-то не было. Думаю, со временем очень многие из них придут к Rust. Я уже видел, что в некоторых научных статьях начали давать примеры кода на Rust.
Вообще, плюсы не учат хорошему вкусу в программировании. Возможно, кстати, для людей с наукоемким софтом это и не так важно: у них задача другая. Но вот когда кода реально много - тут ведь без вариантов.
Многим людям, таким как я, наконец-то дали инструмент, на котором можно творить в потоке: лить код из головы и не отвлекаться на шероховатости языка, перепроверяя себя каждую секунду. С Rust я впервые почувствовал, что такое быть "высокоуровневым" программистом. Это очень крутое и вдохновляющее ощущение. Думаю, за фанатизмом любителей Rust скрывается благодарность за подобную отдушину.
В комментарии выше было отличное видео.
Из моих любимых в наследии C:
звездочка отдельно от типа указателя при объявлении: int *p, *t;
присваивания в операторах
неявные преобразования типов
В плюсах:
порядок объявления членов vs порядок их указания в конструкторе
виртуальное наследование
конструкторы: A(const A&&) {} - разрешено, но зачем; A(A&) {} - пропущен const - и это уже не копирующий конструктор (увы, как-то видел такое в реальном коде, пришлось долго искать причину поломки). сам принцип того, что при объявлении конструктора все может взорваться где-то в другом месте, потому что конструктор по умолчанию больше не генерируется
lvalue, rvalue, prvalue, xvalue (ничего не пропустил?)
тут можно долго продолжать, но зачем?
Я понял правильную аргументацию: возьмите ваш пример с некорректной очисткой вектора и перенесите его в любой другой язык программирования. Ошибка кодирования сохранится, алгоритм будет работать некорректно. А теперь попробуйте сделать то же самое с предложенным мной ниже примером с перемещением.
В этом и есть разница: семантика перемещения - это артефакт отдельного языка, служащий "костылем" для повышения производительности, который сам по себе был рождён в результате неверно принятых концептуальных решений предыдущих этапов.
Думал над вашим ответом в свободное время. Могу его представить, но не могу понять, как из него и из основного доказательства с необходимостью следует теорема Пифагора. Ваше интуитивное понимание на другом уровне.
Обычно системное ПО определяется тем, что оно напрямую взаимодействует с операционной системой и/или с устройствами. Также в его задачи входит самостоятельный учет и контроль ресурсов системы во время выполнения. То есть, в отличие от прикладного ПО, здесь меньше слоев абстракции между кодом и непосредственно железом.
Спасибо! Длинное видео, попробую осилить в свободное время хотя бы на перемотке. Понимаете, на конкретные мелочи я насмотрелся достаточно, но вот прийти к некому обобщению, которое было бы понятным лично мне, долго не удавалось. Эта статья - и есть такое обобщение.
Пальцем:
some_method(...., ...., ..., ...., ...., std::move(object), ..., ...., ..., );иsome_method(...., ...., ..., ...., ...., object, ..., ...., ..., );логически эквивалентны. Остальное - это детали реализации. Плохой реализации.
Огонь! Вы являетесь великолепным образчиком того типа людей, о которых я говорил в статье. Видите - вы связаны с ней гораздо сильнее, чем вам кажется.
Да, вместо этого там есть что-то типа: "the object is left in a valid, but unspecified state". Хорошая попытка.
Отличается принципиально: в подобном коде всегда есть явное намерение автора для достижения некоторой поставленной задачи его алгоритма. Если это нетривиальный ход - желательно его откомментировать. Но я всегда могу сделать git blame и спросить у автора строки его понимание вещей напрямую.
Перемещение 9-го по счету параметра в вызываемый метод - это не более чем артефакт языка, который не несет никакой полезной нагрузки. Более того, его очень просто не заметить.
Я тоже знаю, как должно быть правильно но я все еще не живу в идеальном мире. Разработка за деньги обычно предполагает сроки, взаимодействие с коллегами и это вот все. Увы.