Является ли это приемлемым способом использования частных методов класса в C ?

#c

Вопрос:

В моей программе на C у меня есть класс, в некоторых методах которого выполняются одни и те же процедуры, такие как открытие потоков для чтения/записи в файлы, синтаксический анализ файлов, определение типов mime и т. Д. Те же процедуры используются и в конструкторе. Чтобы сделать методы более компактными и избежать многократного ввода одного и того же кода, я разделил эти рутинные операции на частные методы для использования только внутри класса. Однако некоторые из этих частных методов зависят от результата других, так что вызов этих методов в неправильном порядке может привести к довольно плохим последствиям. Просто глупый пример:

 class Example
{
    public:
        Example(int x);
        ~Example() {}
        //...
        //...
    protected:

    private:
        int a;
        int b;
        bool c;
        void foo_();
        void bar_();
        //...
        //...
};

Example::Example(int x) : a(x)
{
    foo_();
    bar_();
}

void Example::foo_()
{
    if (a == 0)
    {
        b = 10;
    }
    else
    {
        b = a * 2;
    }
}

void Example::bar_()
{
    if (b == 0)
    {
        c = false;
    }
    else
    {
        c = true;
    }
}
 

Как видно из приведенного выше примера, вызов bar_() ранее foo_() в конструкторе приведет к неопределенному поведению, поскольку b он еще не был инициализирован. Но стоит ли мне беспокоиться о таких нюансах, если я определенно уверен, что правильно использую эти частные методы внутри класса, и они никогда не могут быть использованы вне класса?

Комментарии:

1. Да, это так, почему бы и нет…

2. Просто потому, что вы явно не инициализировали его, это не значит, что вы не получаете значение по умолчанию. Однако для ясности вы должны инициализировать его в любых списках конструкторов.

3. Самое главное-добавить документацию, по крайней мере, в виде комментариев или, возможно, в какой-либо другой файл (вы можете использовать LaTeX для написания документации), о вашем коде C . Прочитайте также документацию вашего компилятора C (возможно, GCC , используйте его как g -Wall -Wextra -g …) и вашего отладчика (например, GDB …). И вы могли бы написать документацию по своему коду с помощью карандаша и бумаги. Читайте также книги о программировании на C

4. Чего бы вы ожидали, кроме значения по умолчанию, если бы вы его не инициализировали?

5. Я не вижу UB. Вы не читаете b , когда я вижу, что он неинициализирован.

Ответ №1:

Не говоря уже о том, что то, что вы сделали, является рекомендуемым способом! Всякий раз, когда внутри функции выполняется несколько различных операций, стандартным способом является разделение функции на несколько функций. В вашем случае пользователю эти функции не нужны, поэтому сделать их закрытыми-это лучшее, что вы могли сделать! Когда дело доходит до той части, где «мне нужно позвонить им в определенном порядке», все в порядке, если код нуждается в вызовах в определенном порядке. Я имею в виду, что логично звонить только foo после bar того, как первое зависит от результата последующего. Это не сильно отличается от того, когда вам нужно назначить память int* p , прежде чем использовать ее в качестве массива. Хотя, как объяснили @Basil и многие другие, обязательно правильно документируйте свой код

Ответ №2:

вызов bar_() перед foo_() в конструкторе приведет к неопределенному поведению, поскольку b еще не был инициализирован

Как правило, я всегда явно инициализирую все поля-члены в конструкторе (в частности, те, которые имеют скалярный тип, например указатели или числа, например, ваш a , b , c внутри class Example ). Преимущество: поведение вашей программы более воспроизводимо. Недостаток: скомпилированный код может запускать бесполезную инициализацию (но умные оптимизирующие компиляторы удалят их).

Если вы компилируете с помощью GCC, используйте его как g -Wall -Wextra -g . Обычно это дает вам полезные предупреждения.

Для крупного проекта на C рассмотрите возможность документирования ваших правил кодирования (в отдельном письменном документе, на бумаге, распространяемом среди всех разработчиков в вашей команде) и проверки некоторых из них с помощью плагина GCC. Смотрите также проект ДЕКОДЕРА и статический анализатор исходного кода Bismon, а также статический анализатор Clang (все GCC, Bismon и анализатор Clang являются открытыми исходными кодами, вы можете улучшить их исходный код).

В некоторых случаях генерируется некоторый код C . См. GNU bison, ANTLR, RefPerSys, FLTK, Qt в качестве примеров программных проектов, генерирующих код на C или предоставляющих генераторы кода, генерирующие код на C . На компьютерах x86/64 вы можете генерировать машинный код во время выполнения с помощью ASMJIT или libgccjit и вызывать этот код с помощью указателей функций (в Linux см. также dlopen(3), dlsym(3) и C dlopen minihowto…). Если в вашем программном проекте есть генераторы кода на C (например, с использованием GPP), вы можете убедиться, что сгенерированный код соответствует некоторым вашим соглашениям и инвариантам кодирования. Однако имейте в виду теорему Райса.

Если вы отлаживаете с помощью GDB, прочитайте о его watch командах и точках наблюдения.

Я также знаю о правиле пяти на C .