Недавно мне пришло в голову, что чаще, чем я хотел бы признать, исправление для «странной ошибки, которая возникает только изредка», заключается в простой инициализации переменной-члена, одного класса, который я забыл добавить в список инициализаторов.
Чтобы не тратить время на такие ошибки в будущем, я возился с идеей полностью отказаться от встроенных примитивных типов и заменить их классами-обертками, которые действуют точно так же, как их примитивные аналоги, за исключением того, что они всегда будут инициализироваться.
Я достаточно опытен в C ++, чтобы знать, что я могу писать классы, которые очень близки к этой цели. То есть Я уверен, что могу написать класс MyInt, который ведет себя очень похоже на настоящий int. Но я также достаточно хорошо знаю C ++, чтобы знать, что, возможно, есть одна или две загадочные вещи, которых мне не хватает;)
Кто-нибудь делал что-то подобное раньше? Есть ли какие-либо указатели на соответствующую документацию или список подводных камней, на которые стоит обратить внимание? Это даже хорошая идея, или я не вижу никаких недостатков?
Спасибо!
РЕДАКТИРОВАТЬ: Спасибо всем за ваши комментарии, вот обновление. Я начал играть с фрагментом оболочки Jarod42 и посмотреть, смогу ли я преобразовать небольшую кодовую базу хобби-проекта. Не совсем неожиданно, это довольно PITA, но, вероятно, будет выполнимо. Это начинает чувствовать себя очень большим молотком для этой проблемы.
Предупреждения компилятора не являются реальным вариантом, так как только один (-Weffc ++), похоже, находит проблему и существует только для gcc, то есть это не безопасное, переносимое решение.
Мой вывод на данный момент состоит в том, чтобы начать использовать классовую инициализацию C ++ 11 для всех примитивных членов, как это было предложено Преторианом ниже. То есть что-то вроде
struct C {
int x = 0; // yay C++11!
};
.. и надеемся, что через некоторое время пропущение таких инициализированных ощущений, как «голых», будет означать объявление унитизированной переменной в коде внутри функции (что я давно перестал делать). Это кажется гораздо менее подверженным ошибкам, чем попытка поддерживать список (ы) инициализатора в актуальном состоянии, потому что это прямо там с объявлением.
C ++ 11 позволяет довольно легко избежать этой ловушки, разрешая инициализацию в классе нестатических членов-данных. Например:
struct foo
{
foo(int i) : i(i) {} // here i will be set to argument value
foo() {} // here i will be zero
int i = {}; // value (zero) initialize i
};
Эта функция также не ограничивается тривиальными типами. Так что просто начните инициализировать элементы данных, как только вы объявите их в определении класса.
Если ваш компилятор не поддерживает эту функцию, попробуйте увеличить уровень предупреждения, чтобы узнать, не скажет ли он вам о неинициализированных элементах данных. Например, g ++ имеет -Weffc++
флаг, который предупредит вас о неинициализированных элементах данных (среди прочего).
Следующее, что нужно попробовать, — это инструмент статического анализа, чтобы уловить эти ошибки.
В заключение, есть несколько вещей, которые я бы попробовал, прежде чем идти по пути упаковки каждого тривиального типа данных.
Гораздо проще просто включить предупреждения компилятора. Большинство достойных компиляторов предупредит вас об использовании неинициализированных переменных — при условии, что есть несколько крайних случаев, которые могут пропустить компиляторы.
Попробуй лучше отладить.
Вы можете включить предупреждения компилятора для неинициализированных переменных (см. этот ТАК вопрос).
Вы также можете использовать программы, которые выполняют эту и другую проверку вашего кода (статический анализ), например cppcheck, который перечисляет неинициализированные переменные.
Также попробуйте изменить способ написания кода. В C ++ вы можете контролировать, когда выделяется память, какие конструкторы используются и так далее. Если вы кодируете в стиле, в котором вы создаете объекты с частичными данными, а затем заполняете другие части, то вы, вероятно, будете часто сталкиваться с неинициализированными переменными. Но если вы убедитесь, что все конструкторы создают действительный объект в допустимом состоянии, и избегаете иметь несколько точек истинности (см. Принцип «Единой точки истины»), то ваши ошибки с большей вероятностью будут обнаружены компилятором — у вас будет передать неинициализированную переменную в качестве значения (о чем вас предупредит VC ++) или указать неверный номер или тип вещей в вызове конструктора (ошибка компиляции) и т. д.
Могу ли я предложить вам выбрать ваш самый последний источник такого рода вещей, дополненный цепочкой структур, которые вас туда привели, и спросить, как вы могли бы реструктурировать его лучше? В C ++ существует особенно дисциплинированный стиль кодирования, который максимально использует компилятор и, таким образом, дает вам советы как можно раньше. На самом деле ошибки, которые вы создаете при использовании этого стиля, не должны быть чем-то меньшим, чем проблемы многопоточности, проблемы с ресурсами и т. Д.
Я беспокоюсь о том, что если вы инициализируете все только для того, чтобы предотвратить такие ошибки, вы упустите возможность изучить этот дисциплинированный стиль, который намного шире, чем неинициализированные переменные.
Следующее может помочь:
template <typename T>
class My
{
public:
constexpr My() : t() {}
constexpr My(const T&t) : t(t) {}
operator T& () { return t; }
constexpr operator const T& () const { return t; }
const T* operator& () const { return &t; }
T* operator& () { return &t; }
private:
T t;
};
Обратите внимание, что если лучше проверить, My<int>
используется вместо каждого возможного неинициализированного int
…
Но обратите внимание, что вы должны сделать специальную работу для union
тем не мение.