Comments 26
Интересно. Спасибо.
В современном C++ не вижу ни одной причины не писать вот так:
struct Response {
bool error = false;
bool succeeded = false;
std::string data;
};И все вопросы про инициализацию снимаются.
P.S. статья по теме https://habr.com/ru/companies/jugru/articles/469465/ и легендарная гифка https://habrastorage.org/webt/bp/rd/ow/bprdow1rk6jtw5fzpm3bffzirps.gif
Зачем столько часов было тратить на изучение особенностей разных компиляторов? Понятно же, что поля не проинициализированы. Можно быстро исправить и потратить время на размышления о том, как плохо программировать на флагах.
Можно поподробнее про программирование на флагах?
Программирование на флагах - это когда одно логическое состояние размазано по нескольким независимым булам. Как в статье: error и succeeded по смыслу взаимоисключающие, но как два bool дают 4 комбинации при 2 валидных. Ни одно поле не «владеет» ответом - единого источника правды нет, инвариант держится на дисциплине, а не на типе.
И тут важный момент: инициализация полей структуру не спасает. Допустим, починили память:
struct Response {
bool error = false;
bool succeeded = false;
std::string data;
};Языковой UB из статьи ушёл - мусора в памяти больше нет. Но {error:true, succeeded:true} по-прежнему может быть получено вручную в коде. Это уже не неопределённое поведение в смысле стандарта, а логический UB: тип разрешает состояние, которого по смыслу быть не может, и программа однажды в него попадёт. Инициализация лечит симптом, а не причину.
Лечится тем, что за исход отвечает одно поле:
enum class Outcome { Success, Failure };
struct Response {
Outcome outcome = Outcome::Failure;
std::string data;
};Теперь ответственность за состояние лежит на одном outcome, невозможных комбинаций физически нет, а новый код не сможет случайно выставить «и успех, и ошибку». UB в статье просто подсветил проблему программирования на флагах - корень в том, что взаимоисключающий выбор смоделировали через независимые флаги.
Изучение стандарта никогда не бывает лишним. Человек один раз разобрался, как работает инициализация по умолчанию, и теперь точно не наступит на эти грабли снова
Феерично! Это идеальный пример того как ссылки на "неопределенное поведение" маскируют не понимание не только С++, но вообще основ программирования!
Автору просто по фигу что если одно и тоже значение передавать два раза / скопировать в два поля, причем во второе с инверсией, то получается не два, а четыре возможных значения. По сути это и вызывает наивное удивление и жалобы о том что в С++ все очень неопределенно. Только при чем здесь С++. Если bool скопировать два раза то возможны ЧЕТЫРЕ варианта, чему здесь удивляться, при чем здесь С++ ?
правильно будет что-то на базе:
struct Response {
std::optinal<bool> error;
std::optinal<bool> succeeded;
};или
struct Response {
bool error -> !res;
bool succeeded -> res;
privite:
std::optinal<bool> res;
};Но чтобы сделать правильно это же напрягаться-думать надо, гораздо проще жаловаться на неопределенное поведение и получать зарплату за бесконечное исправление чужих, а потом и собственных багов и лавры мастера по борьбе с UB.
По моему, с точки зрения архитектуры, структура передаваемого значения в виде
{ "error": false, "succeeded": true }
означает, что проектировщики этого закладывали, что возможны ситуации, когда оба значения равны true или false, просто их надо правильно обрабатывать.
Если была необходимость устанавливать в true только одного поля, передавалось бы только одно поле.
Ожидал продолжения, что исправление завалило прод с убытками на миллиарды евро
Не знаю ни C, ни С++, поэтому стесняюсь спросить - а почему ОДНОЙ булевой переменной недостаточно в данной ситуации? Всего одной...
Скорее всего историческое наследие. Сначала сделали success, потом кто-то решил, что нужен еще флаг error для совместимости с другим кривым апи. И так оно и поехало в прод
Возможно нужно было 3 статуса: success, failure, error
Мне кажется абсолютно логично было бы вернуть в этой ситуации одно перечисление с тремя состояниями. Взаимоисключение в этом случае было бы невозможно, а 'лишние' значения ловились бы элементарно приемником (что лучше всегда и делать)
Автоматические переменные надо инициализировать если им что то сразу же не присваивается - не проще ли запомнить это простое правило, вместо изучения диалектов и имплементаций языка? Если эта переменная не в цикле на 100500 тыщ, то вообще всё равно, что там компилятор нагенерирвет на самое неэффективное, но идеологичесаи выдержанное решение)
И да, разве сонар и компания не подсвечивают такое прямо в ide?
Здесь прекрасно всё -- и зачем-то два поля в ответе с обязательно противоположными значениями (видимо, когда-то планировалось сделать error строкой и класть туда ошибку, но потом решили сделать какую-то дичь вместо этого), и наивная вера в то, что кто-то за тебя проинициализирует память.
Правило 3 и правило 6 здесь вообще неактуальны - структура не управляет никакими ресурсами, которые нужно вручную копировать или перемещать. Ну это я так, для читающих комментарии, а по статье понятно, что новичок писал.
А в багрепорте сообщалось, что клиент получил следующий отклик:
{ "error": true, "succeeded": true }
Это норма. :)

База С++. Пишешь Response response;, получаешь мусор в памяти и гадаешь, в какой фазе луны компилятор решит это занулить)
Правило одно: всегда инициализируй явно, даже если кажется, что оно само
TL;DR: В пафосных конторах, которые обрабатывают платежи на милларды евро, работают такие же разъебаи как и ты. Но гонору-то на собесах!
Не плюсы, но когда только начинал работать эмбеддером и прогал всякие мк, трогая всякие либы sdk, где железо инициализировалось заполнение структуры, выучил простое правило, заполняй все поля структуры которую выделил на стеке, даже если эти поля не нужны.
Разве можно в C++ оставлять неинициализированные переменные. Опытный разработчик даже не будет поднимать такой вопрос.
я правильно понял, что в случаях: Response response и Response response{} - вызываются 2 разных конструктора, где в 1 предусмотрено только std::string(), а во 2 еще error() и success(), которые компилятор генерит исходя их кода использования объекта?
Смысл всей басни такова: Не смотря C++, мы продолжаем его любить :)
Один баг в проде, после которого я всерьёз воспринимаю неопределённое поведение