重构构造函数以使用初始化列表

Posted

技术标签:

【中文标题】重构构造函数以使用初始化列表【英文标题】:Refactor constructor to use initialization list 【发布时间】:2016-11-14 15:09:02 【问题描述】:

我正在处理一个非常大的代码库(超过 3M loc),我们显然有很多类,但它们中的大多数不在其构造函数中使用初始化列表,而是在构造函数主体中分配值(一些代码是很久以前编写的,因此这已成为事实上的标准)。也许这些被编译器优化掉了,但我不确定是否真的如此。

我正在尝试推广初始化列表的使用,但是有大量的代码库需要更新,那么是否有任何工具可以自动为我完成这项工作?将它指向一个类,找到所有 m_var = 0; 行并将它们移动到初始化列表(如果需要,创建它)。

除了将体内初始化转换为初始化列表之外,有没有办法找出成员变量以正确的顺序初始化(即与它们在类的头文件中定义的顺序相同?我是希望 CppCheck 能接受这个,但似乎没有。

【问题讨论】:

至于第二部分:GCC有-Wreorder @Biffen 是否使用 Visual Studio 2012?因为这就是我正在使用的。 不是我所知道的,快速的谷歌没有给出任何信息。我会留给你做更多的研究。 【参考方案1】:

您好,我是一名 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 代码库的大小一样,需要的是program transformation system (PTS)。这是一个将目标语言源文件解析为编译器数据结构(通常是 AST)的工具,允许您对 AST 应用转换,然后可以重新生成有效的源代码,包括修改后的程序的原始 cmets。将 PST 视为大规模重构的工具

一个好的 PTS 会让你写出 source-to-source transforms 的形式:

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

其中 thisthat 以目标语言语法表示,其中 this 仅在源代码与显式语法匹配时才匹配。 [这些不是字符串匹配;他们在 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  ";

The syntax of these rules/patterns for our DMS Software Reengineering Toolkit is explained here。 DMS 是我所知道的唯一可以处理 C++ 的源到源 PTS;它甚至可以处理 MSVS 方言]。

我遗漏了一个可能必要的“if 条件”检查成员名称确实是类的成员,假设您的构造函数没有滥用。

因为你的构造函数可能没有任何初始化列表,你需要一个辅助规则来在必要时引入一个:

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 向means to access a symbol table 提供此信息]。

或者,您可以简单地使用不同的重写规则重新排序它们:

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);

这个规则本质上是对初始化器进行排序。 [注意这是一个冒泡排序:但初始化器列表往往不会很长,而且无论如何你只会对每个构造器运行一次。] 在你将所有初始化器从构造器主体中取出后运行这个规则前面显示的规则。

【讨论】:

以上是关于重构构造函数以使用初始化列表的主要内容,如果未能解决你的问题,请参考以下文章

C++ 构造函数初始化列表

为什么 没有缺省构造函数的类类型成员 必需要在初始化列表 里初始化 ?

为什么 没有缺省构造函数的类类型成员 必需要在初始化列表 里初始化 ?

构造函数初始化

类初始化与构造函数初始化列表的顺序

C++成员初始化列表(构造函数后加冒号:)(用于在构造函数中初始化类成员变量,可以避免使用构造函数体内的赋值语句,可以确保成员变量在对象构造之初就已经被正确初始化,提高代码的性能和可读性)