Google Summer of Code: C++ Modernizer 改善
Google Summer of Code (GSoC) 为学生提供奖学金,让他们在夏季参与开源项目。今年,我被选中参与 Clang C++ Modernizer 项目,该项目以前称为 C++11 Migrator,由英特尔团队驱动。该工具的目标是通过使用新 C++ 标准的新功能来现代化 C++ 代码,从而提高可维护性、可读性和编译时间和运行时性能。该项目曾在 4 月份的博客文章 “C++11 Migrator 的现状” 中被介绍,并且一直在不断发展,包括架构和功能方面。
本文介绍了该工具在过去几个月中的改进,包括我今年夏天为 GSoC 做的贡献。有关该工具的完整概述以及如何安装它,请访问文档:https://clang.llvm.net.cn/extra/clang-modernize.html#getting-started。要演示该工具,您可以查看 Chandler Carruth 在 Going Native 2013 上的演讲:The Care and Feeding of C++'s Dragons。clang-modernize 的展示从大约 33 分钟开始。
为了避免更改不应该更改的文件,例如系统头文件或第三方库的头文件,有一些选项可以控制哪些文件应该被转换。
启用此功能的命令行开关是 -override-macros。
示例
示例
该转换目前仅限于复制到类字段的构造函数参数。
示例
std::move()是 <utility> 中声明的库函数<utility>。如果需要,clang-modernize 会添加此头文件。
这种转换还有很大的改进空间。可能存在其他可以安全转换的情况。热烈欢迎对此方面的贡献!
示例
void f(const std::vector<int> &my_container) {
for (std::vector<int>::const_iterator I = my_container.begin(),
E = my_container.end();
I != E; ++I) {
std::cout << *I << std::endl;
}
}
不重新格式化
void f(const std::vector<int> &my_container) {
for (auto I = my_container.begin(),
E = my_container.end();
I != E; ++I) {
std::cout << *I << std::endl;
}
}
重新格式化
void f(const std::vector<int> &my_container) {
for (auto I = my_container.begin(), E = my_container.end(); I != E; ++I) {
std::cout << *I << std::endl;
}
}
有关此选项的更多信息,请查看文档:格式化命令行选项.
例如,假设您的项目放弃了对编译器“旧”版本的依赖。您可以自动将代码现代化为要支持的编译器的最新最小版本。
要支持 Clang >= 3.1、GCC >= 4.6 和 MSVC 11
有关此选项的更多信息,以及查看每个编译器可以使用哪些转换,请阅读文档.
更多转换正在发布,以及对现有转换的改进,例如传值转换。
我们将继续修复错误并添加新功能。我们的积压是公开可用的:https://cpp11-migrate.atlassian.net/secure/RapidBoard.jspa?rapidView=1&view=planning
现代化器有自己的错误和项目跟踪器。如果你想提交或修复错误,请访问:https://cpp11-migrate.atlassian.net
其他一些需要牢记的地址
-- Guillaume Papin
转换构成翻译单元的所有文件
与上一个版本相比,一个主要改进是能够转换构成翻译单元的所有文件,而不仅仅是主源文件。这意味着如果需要,头文件也会被转换,这使得现代化器更加有用。为了避免更改不应该更改的文件,例如系统头文件或第三方库的头文件,有一些选项可以控制哪些文件应该被转换。
- -include 接受一个逗号分隔的路径列表,这些路径允许被转换。整个目录树中每个给定路径下的所有文件都被标记为可修改的。出于安全考虑,默认行为是不转换任何额外文件。
- -exclude 接受一个逗号分隔的路径列表,这些路径禁止被转换。可以用来从包含的目录树中修剪子树。
- -include-from 和 -exclude-from 分别等效于 -include 和 -exclude,但以文件名作为参数而不是逗号分隔的路径列表。该文件应包含每行一个路径。
- src/foo.cpp
- include/foo.h
- lib/third-party.h
clang-modernize -include=include/ src/foo.cpp -- -std=c++11 -I include/ -I lib/
clang-modernize -include=. -exclude=lib/ src/foo.cpp -- -std=c++11 -I include/ -I lib/
转换
现在总共有 6 个转换,其中 2 个是新的- Add-Override 转换
为覆盖的成员函数添加 'override' 说明符。 - 循环转换
使用基于范围的 for 循环。 - 按值传递转换 [新增]
替换 const-ref 参数,这些参数将从使用按值传递习语中受益。 - 替换 Auto-Ptr 转换 [新增]
替换对已弃用的std::auto_ptr的用法std::unique_ptr. - 使用 Auto 转换
在变量声明中使用 auto 类型说明符。 - 使用 Nullptr 转换
在适用时用 nullptr 替换空文字和宏。
Add-Override 的改进
自 4 月份的上一篇文章以来,Add-Override 转换已得到改进,可以处理用户定义的宏。某些项目(如 LLVM)使用一个宏扩展为 'override' 说明符,以与不符合 C++11 的编译器保持向后兼容性。clang-modernize 可以检测这些宏并使用它们,而不是使用 'override' 标识符。启用此功能的命令行开关是 -override-macros。
示例
clang-modernize -override-macros foo.cpp
之前 | 之后 |
#define LLVM_OVERRIDE override struct A { virtual void foo(); }; struct B : A { virtual void foo(); }; | #define LLVM_OVERRIDE override struct A { virtual void foo(); }; struct B : A { virtual void foo() LLVM_OVERRIDE; }; |
使用 Nullptr 的改进
此转换也已得到改进,可以处理类似于 NULL 的用户定义的宏。用户可以使用命令行开关 -user-null-macros=<string> 指定哪些宏可以被 nullptr 替换。示例
clang-modernize -user-null-macros=MY_NULL bar.cpp
之前 | 之后 |
#define MY_NULL 0 void bar() { int *p = MY_NULL; } | #define MY_NULL 0 void bar() { int *p = nullptr; } |
新增转换:替换 Auto-Ptr
此转换是 GSoC 工作的结果。该转换替换了对std::auto_ptr的用法std::unique_ptr的用法。它还会在需要时插入对std::move()的调用。之前 | 之后 |
#include <memory> void steal(std::auto_ptr<int> x); void foo(int i) { std::auto_ptr<int> p(new int(i)); steal(p); } | #include <memory> void steal(std::unique_ptr<int> x); void foo(int i) { std::unique_ptr<int> p(new int(i)); steal(std::move(p)); } |
新转换:按值传递
这也是 GSoC 的产物,该转换利用了 C++11 中添加的移动语义,以避免对接受具有移动构造函数类型的函数进行复制。通过更改为按值传递语义,如果提供了右值参数,则可以避免复制。对于左值参数,复制次数保持不变。该转换目前仅限于复制到类字段的构造函数参数。
示例
clang-modernize pass-by-value.cpp
之前 | 之后 |
#include <string> public: A(const std::string &Copied, const std::string &ReadOnly) : Copied(Copied), ReadOnly(ReadOnly) {} private: std::string Copied; const std::string &ReadOnly; }; | #include <string> #include <utility> class A { public: A(std::string Copied, const std::string &ReadOnly) : Copied(std::move(Copied)), ReadOnly(ReadOnly) {} private: std::string Copied; const std::string &ReadOnly; }; |
std::move()是 <utility> 中声明的库函数<utility>。如果需要,clang-modernize 会添加此头文件。
这种转换还有很大的改进空间。可能存在其他可以安全转换的情况。热烈欢迎对此方面的贡献!
可用性改进
我们还努力提高现代化程序的整体可用性。调用现代化程序现在需要更少的参数,因为大多数情况下可以推断出参数。- 如果没有提供编译数据库或标志,则假定 -std=c++11。
- 默认情况下启用所有转换。
- 如果提供了编译数据库,则不需要显式列出文件。现代化程序将从编译数据库中获取文件。使用 -include 选择要使用的文件。
- 使用 LibFormat 自动重新格式化受转换影响的代码。
- 一个新的命令行开关,用于根据编译器支持选择要应用的转换。
重新格式化转换后的代码
LibFormat 是 clang-format 背后的库,clang-format 是一个用于格式化 C、C++ 和 Obj-C 代码的工具。clang-modernize 也使用此库来重新格式化转换后的代码。当使用 -format 启用时,默认样式为 LLVM。-style 选项可以以与 clang-format 相同的方式控制样式。示例
format.cpp
#include <iostream>
#include <vector>
#include <vector>
void f(const std::vector<int> &my_container) {
for (std::vector<int>::const_iterator I = my_container.begin(),
E = my_container.end();
I != E; ++I) {
std::cout << *I << std::endl;
}
}
不重新格式化
$ clang-modernize -use-auto format.cpp
#include <iostream>
#include <vector>
#include <vector>
void f(const std::vector<int> &my_container) {
for (auto I = my_container.begin(),
E = my_container.end();
I != E; ++I) {
std::cout << *I << std::endl;
}
}
重新格式化
$ clang-modernize -format -style=LLVM -use-auto format.cpp
#include <iostream>
#include <vector>
#include <vector>
void f(const std::vector<int> &my_container) {
for (auto I = my_container.begin(), E = my_container.end(); I != E; ++I) {
std::cout << *I << std::endl;
}
}
有关此选项的更多信息,请查看文档:格式化命令行选项.
根据编译器支持选择转换
另一个有用的命令行开关是:-for-compilers。此选项启用给定编译器支持的所有转换。例如,假设您的项目放弃了对编译器“旧”版本的依赖。您可以自动将代码现代化为要支持的编译器的最新最小版本。
要支持 Clang >= 3.1、GCC >= 4.6 和 MSVC 11
clang-modernize -format -for-compilers=clang-3.1,gcc-4.6,msvc-11 foo.cpp
有关此选项的更多信息,以及查看每个编译器可以使用哪些转换,请阅读文档.
下一步是什么?
并行转换多个翻译单元的能力将很快到来。想想像 make 和 ninja 一样的clang-modernize -j。结果,大型代码库的现代化将变得更快。更多转换正在发布,以及对现有转换的改进,例如传值转换。
我们将继续修复错误并添加新功能。我们的积压是公开可用的:https://cpp11-migrate.atlassian.net/secure/RapidBoard.jspa?rapidView=1&view=planning
参与进来!
对该工具感兴趣?发现错误?有可以对其他人有用的转换的想法?该项目是开源的,非常欢迎贡献!现代化器有自己的错误和项目跟踪器。如果你想提交或修复错误,请访问:https://cpp11-migrate.atlassian.net
其他一些需要牢记的地址
- Clang C++ Modernizer 用户手册
- IRC 频道:irc.oftc.net 上的 #llvm
- 邮件列表
- cfe-dev 用于问题和一般讨论
- cfe-commits 用于补丁
- Phabricator 提交补丁
最后的话
最后,我要感谢我的导师 Edwin Vane 及其在英特尔的团队、Tareq Siraj 和 Ariel Bernal 为我提供的巨大支持。还要感谢 LLVM 社区和 Google 暑期代码计划团队,让我有机会在这个夏天参与 C++ Modernizer 的工作。-- Guillaume Papin