Refactor конструктор для использования списка инициализации

Я работаю над очень большой базой кода (более 3M loc), у нас, очевидно, много классов, но большинство из них не используют списки инициализации в своих конструкторах, вместо этого они присваивают значения в теле конструктора (часть кода была написана Долгое время назад, так что это стало стандартом де-факто). Возможно, они оптимизированы компилятором, но я не уверен, что это действительно так.

Я пытаюсь продвигать использование списков инициализации, но есть большая база кода, которая нуждается в обновлении, поэтому есть ли инструменты, которые сделают это для меня автоматически? Укажи это на класс, найди все m_var = 0; линии и переместите их в список инициализации (создавая его при необходимости).

Помимо преобразования инициализации в теле в списки инициализации, есть ли способ узнать, что переменные-члены инициализируются в правильном порядке (т.е. в том же порядке, в котором они определены в заголовочном файле класса? Я надеялся, что CppCheck подхватил бы это, но это не кажется.

2

Решение

Здравствуйте, я разработчик cppcheck.

Cppcheck также имеет проверку на несоответствие порядка. Но это неокончательная проверка.

Например:

class Fred {
public:
Fred() : y(0), x(0) {}
int x;
int y;
};

Вывод Cppcheck:

daniel@debian:~/cppcheck$ ./cppcheck --enable=style --inconclusive 1.cpp
Checking 1.cpp ...
[1.cpp:3] -> [1.cpp:4]: (style, inconclusive) Member variable 'Fred::x' is in the wrong place in the initializer list.

Наша простая проверка будет просто предупреждать о несоответствии заказа. Вот почему это неокончательно. В приведенном выше коде порядок инициализации на самом деле не имеет значения — поскольку все члены в приведенном выше коде являются целыми числами, а все инициализаторы являются константами.

2

Другие решения

С таким масштабом, как размер кодовой базы OP, нужно система трансформации программ (PTS). Это инструмент, который анализирует исходный файл целевого языка в структурах данных компилятора (обычно AST), позволяет применять преобразования к AST, а затем может восстанавливать действительный исходный код, включая исходные комментарии для измененной программы. Думайте о PST как инструментах для рефакторинг в целом.

Хороший ПТС позволит вам написать преобразования источник-источник формы:

when you see *this*, replace it by *that* if *condition*

где этот а также тот выражены в синтаксисе целевого языка, где этот соответствует только в том случае, если исходный код соответствует явному синтаксису. [Это не совпадения строк; они работают на AST, так что расположение не влияет на их способность совпадать].

Вам нужно ключевое правило, которое выглядит примерно так:

rule move_to_initializer(constructor_name:IDENTIFIER,
arguments: argument_list,
initializer_list: initializer,
member_name:IDENTIFIER,
initializer_expression: expression,
statements: statement_list
): constructor -> constructor =
" \constructor_name(\arguments): \initializer_list
{ \member_name = \initializer_expression ;
\statements } "->  " \constructor_name(\arguments): \initializer_list, \member_name(\initializer_expression)
{ \statements } ";

Синтаксис этих правил / шаблонов для нашего DMS Software Reengineering Toolkit объясняется здесь. DMS — единственная PTS от источника к источнику, которую я знаю, которая может обрабатывать C ++; это даже обращается с диалектом MSVS].

Я пропустил возможно необходимое «если состояниеmsgstr «проверьте, что имя члена действительно является членом класса, при условии, что ваши конструкторы не являются оскорбительными.

Поскольку ваши конструкторы могут не иметь никакого списка инициализатора, вам нужно вспомогательное правило, чтобы ввести его при необходимости:

rule move_to_initializer(constructor_name:IDENTIFIER,
arguments: argument_list,
member_name:IDENTIFIER,
initializer_expression: expression,
statements: statement_list
): constructor -> constructor =
" \constructor_name(\arguments)
{ \member_name = \initializer_expression ;
\statements } "->  " \constructor_name(\arguments): \member_name(\initializer_expression)
{ \statements } ";

{ \member_name = \e ; } "

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

Что касается проверки порядка инициализации, вы можете запустить такую ​​проверку с помощью шаблона (DMS):

pattern check_initializer_order(constructor_name:IDENTIFIER,
initializer_list: initializer,
statements: statement_list
): constructor =
" \constructor_name(): \initializer_list,
{ \statements } "if complain_if_not_ordered(constructor_name,initializer_list);

который требует вспомогательного мета-предиката, который проверяет порядок, который жалуется, если они неупорядочены. Вам нужно constructor_name, чтобы позволить предикату искать соответствующий класс и проверять порядок членов. [DMS предоставляет означает доступ к таблице символов с этой информацией.

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

rule order_initializers(constructor_name:IDENTIFIER,
arguments: argument_list,
initializer_list_prefix: initializer,
initializer_list_suffix: initializer,
member1_name:IDENTIFIER,
initializer1_expression: expression,
member2_name:IDENTIFIER,
initializer2_expression:expression,
statements: statement_list
): constructor -> constructor =
" \constructor_name(\arguments):
\initializer_list_prefix,
\member1_name(\initializer1),
\member2_name(\initializer2),
\initialize_list_suffix
{ \statements } "->
" \constructor_name(\arguments):
\initializer_list_prefix,
\member2_name(\initializer2),
\member1_name(\initializer1),
\initialize_list_suffix
{ \statements } "if is_wrong_order(constructor_name,member1_name,member2_name);

Это правило по существу сортирует инициализаторы. [Обратите внимание, что это пузырьковая сортировка: но списки инициализаторов имеют тенденцию быть не длинными, и вы все равно будете запускать это только один раз для конструктора.] Вы бы запустили это правило после того, как вы удалили все инициализаторы из тела конструктора, используя правила, показанные ранее.

1