#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 …). И вы могли бы написать документацию по своему коду с помощью карандаша и бумаги. Читайте также книги о программировании на C4. Чего бы вы ожидали, кроме значения по умолчанию, если бы вы его не инициализировали?
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 .