cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Неявная инициализация переменных значением по умолчанию #395

Open apolukhin opened 3 years ago

apolukhin commented 3 years ago

Перенос предложения: голоса +6, -13 Автор идеи: valera_ee

Работая над большим проектом, который начал своё начало ещё до С++11, столкнулся с проблемами адаптации кода под новый стандарт. Все поля класса инициализируются в конструкторе или в списке инициализации, около 95% такой инициализации это присваивание переменным класса 0 или NULL.

class ClassName
{
public:
    ClassName(ClassParent *p);
    ~ClassName();

    void setValue(int value);
    int value() const;

private:
    ClassParent *pParent;
    bool m_enable;
    int  m_value;
};

Сейчас можно прописать инициализацию в заголовочном файле:

class ClassName
{
public:
    ClassName(ClassParent *p);
    ~ClassName();

    void setValue(int value);
    int value() const;

private:
    ClassParent *pParent {nullptr};
    bool m_enable {false};
    int  m_value  {0};
};

Возникает логичный вопрос, почему изначально не инициализировать базовые типы нулём, а указатели значением nullptr ? А там где требуется указать значение, отличное от значения по умолчанию, там прописывать вручную.

Был случай когда указатель забыли проинициализировать и там по мусорному адресу происходило обращение и программа не падала при этом, если бы указатель был нулевым, то ошибку обнаружили бы сразу.

apolukhin commented 3 years ago

BlackMat MATov, 26 декабря 2018, 14:03 П - перформанс

Дмитрий, 26 декабря 2018, 23:15

Был случай когда указатель забыли проинициализировать ...

Используйте smart pointer

valera_ee, 27 декабря 2018, 11:39 Дмитрий, сейчас, конечно только ими ипользуюсь, но что делать с сотнями тысяч строк старого кода? Их все разом переписать не получится, это процесс долгий и затратный.

al-mission-2016, 28 декабря 2018, 1:33 При проектировании языка программирования приходится решать дилемму - производительность vs "меньше ошибок по невнимательности". Когда в конце 60х изобретали С, инициализация по-умолчанию была непозволительной роскошью.

Относительно инициализации дилемма такова: [A] по-умолчанию ничего не инициализируется; безопасные начальные значения - это ответственность программиста; [B] всё инициализируется по-умолчанию, за исключением случаев, когда явно указано, что инициализировать не надо.

В С++ очевидно принят подход [А] и это решение слишком фундаментально, чтобы его можно было поменять. Можно только помочь бороться с последствиями. :)

Причины подхода [А] в С++: 1) Совместимость с языком С - там нет инициализации по-умолчанию. 2) Производительность = как основа философии С/С++. Например, в 3D-app требуется создать 1'000'000 треугольников. Нулевые значения смысла не имеют, всё равно они будут перезаписаны при загрузке сцены. Здесь нулевая инициализация - performance hit. Компилятор далеко не всегда может устранить лишние присваивания. 3) требования обратной совместимости с предшествующими версиями С++, чтобы избежать изменения поведения старого кода, при компиляции в режиме С++11++. 4) для пользовательских типов имеются: конструкторы и их списки инициализации, а в С++11/14 добавили инициализация членов-данных при их декларации. Этих инструментов достаточно, чтобы обеспечить значения по-умолчанию, где это необходимо. Кроме того, локальные переменные в С++ рекомендуется создавать как можно ближе к месту их использования и соответственно сразу инициализировать их разумными значениями.

Теперь предположим, что мы изменили С++, введя инициализацию по-умолчанию. Одновременно потребуется добавлять механизм, позволяющий явно указать, как не инициализировать в случаях, когда важна производительность, а значения по-умолчанию не подходят. Напр.:

int x = void; // as in D-language

В результате придётся дорабатывать тот legacy-code, в котором инициализация по-умолчанию начнёт бить по производительности! То есть решая проблему одних, мы создадим проблему другим. Не стоит оно того. :)

valera_ee

Попробуйте статические анализаторы кода.

valera_ee, 28 декабря 2018, 7:19 al-mission-2016, это всё, конечно, хорошо, но вместо голых быстрых указателей мы начали использовать медленный, но безопасный вариант умных указателей, и производительность падает, вместо голых массивов std::vector и это всё замедляет программу, так о какой производительности тогда говорить? Сейчас вообще есть constexpr, мы получили возможность инициализировать константы на этапе компиляции, так , а что мешает делать тоже самое для полей класса у которых отсутствует явная инициализация?

Fihtangolz, 29 декабря 2018, 15:29 valera_ee, хз я вообше предлагаю просто выкинуть фундаментальные типы написать классы int, float, double, оставить только что то типа byte в качестве фундаментального типа, ну собственно дальше кому надо сделают инициализацию по умолчанию

al-mission-2016, 2 января 2019, 8:19

Fihtangolz, в рамках собственного проекта можно делать что угодно, хоть на потолке спать. (: Только вряд ли стоит тащить в стандарт языка обратно-несовместимые вещи, как предложил Валерий. :)

valera_ee,

ваше предложение означает поменять правила игры на лету, когда решение уже давно принято и куча легаси кода полагается на него.

"что мешает делать тоже самое для полей класса у которых отсутствует явная инициализация?"

Мешает обратная совместимость. Часть кода (правда довольно специфичного) просто поломается. Например, поломается код, который принципиально предполагает (в соответствии с нынешним стандартом), что если поле класса нигде явно не проинициализированно, то компилятор в него точно ничего не запишет. Это важно для embedded/firmware программирования. Используя placement-new (и volatile) можно отобразить структуру, т.е. её члены-данные, на память, которая является пространством портов ввода-вывода. Теперь получаем, что "старые" версии компилятора ничего не писали в поля-порты без ведома программиста. А "новый" компилятор, инициализируя поля значениями по-умолчанию, запишет в порты нули! В принципе, этого примера достаточно, чтобы отклонить ваше предложение.

По производительности. Да, компьютеры стали быстрее, память дешевле, можно позволить размен - чуть медленнее софт, зато потенциально чуть меньше ошибок, код пишется быстрее и т.д. Для многих проектов это вполне приемлемо. Но заметьте, std::vector, умные указатели и т.п. - это опция, у нас есть выбор. Если нужна максимальная производительность, можно обойтись без них. А ваше предложение - всегда инициализировать - принудительно. Кстати, некоторые game-dev'ы, типа EA, писали в своё время собственные версии STL, когда их не устраивала производительность контейнеров общего назначения.

apolukhin commented 10 months ago

В https://wg21.link/P2723 делают автоматическую инициализацию переменных, в рамках того же предложения хотят добавить механизм для явной инициализации мусором