原文地址:http://blog.llvm.org/2013/04/status-of-c11-migrator.html
自从2012年12月早些时候,C++11迁移器工具cpp11-migrate的设计文档发布以来,我们的开发工作进展顺利。在这篇文章里,我将介绍一下cpp11-migrate目前已经实现了哪些功能,即将实现哪些功能,以及如何加参与到这个项目里来。
cpp11-migrate的目标是实现一个C++11迁移器,这个迁移器可以实现源码到源码的转换,使用C++11的新特性去迁移已有的C++代码,从而提高这些已有的C++代码的可维护性、可读性、运行性能以及缩短编译性能。开发工作仍然处于早期阶段,目前的转换器主要分为两类。这个迁移器是基于Clang的LibTooling和AST Matching library。
目前所有的开发工作是Intel的一个小核心组在进行。我们目前工作的中心是建立起基础平台和测试,实现一小部分基本的转换器,同时确保这些转换器工作良好。我们的目标是希望可以这个工具对社区是实用的,所以我们总是聆听转换器的想法和反馈。
如何获得cpp11-migrate
cpp11-migrate位于Clang额外工具包。构建cpp11-migrate,你将需要LLVM和Clang的源码。跟随Clang的 Getting Started instructions 指引,确保履行下载了可选的额外的工具包。一旦下载到了构建系统对应的目录,重新认证之后,将自动包含Clang额外工具包作为接下来整体构建的一部分。如果你使用的是CMake构建系统,你可以构建 cpp11-migrate通过使用cpp11-migratetarget参数。CMake提供的 check-clang-tools将运行所有Clang额外工具包括cpp11-migrate的回归测试。目前已经实现的转换器
这个C++11迁移器目前支持C++11的四个特性:
- 基于范围的for循环
- 为空指针提供了nullptr关键字
- auto类型说明符
- override虚拟说明符
基于范围的for循环转换器曾经作为一个单独的叫做loop-convert的工具存在过,它的贡献者是Sam Panzer。当打算开发更多的转换器的时候,实现的思路就变成了把所有的转换器放在一个单独的工具管辖之下,于是 cpp11-migrate诞生了。基于范围的for循环转换器替换了下列三种普遍情况下的任何一种for循环:
- 使用迭代器遍历容器:
std::vector<int> myVec; for (std::vector<int>::iterator I = myVec.begin(), E = myVec.end(); I != E; ++I) llvm::outs() << *I;
⇒std::vector<int> myVec; for (auto & elem : myVec) llvm::outs() << elem;
- 遍历静态数组:
int arr[] = {1,2,3,4,5}; for (int i = 0; i < 5; ++i) llvm::outs() << arr[i];
⇒int arr[] = {1,2,3,4,5}; for (auto & elem : arr) llvm::outs() << elem;
- 使用操作符[]或者()遍历类似数组的容器:
std::vector<int> myVec; for (int i = 0; i < myVec.size(); ++i) llvm::outs() << v[i];
⇒std::vector<int> myVec; for (auto & elem : myVec) llvm::outs() << elem;
nullprt转换器使用最新的nullprt关字,当指针被被初始化或者赋值为一个空值的时候。万一这时候还有一个显式的转换存在,这个显示的转换将保留下来,避免引入两意性到代码。
auto类型说明符转换器使用新的auto关键字替换了变量声明的类型说明符。一般来说,只要变量声明的类型和它的初始化类型相匹配,这样的替换就可以做。当然,这个转换器只是针对一小部分特殊的方便可读性和可维护性的实用场景:
void foo(int *arg); void foo(float *arg); int *IntPtr = 0; float *FloatPtr = NULL; foo(static_cast<int*>(0));
⇒
void foo(int *arg); void foo(float *arg); int *IntPtr = nullptr; float *FloatPtr = nullptr; foo(static_cast<int*>(nullptr));
auto类型说明符转换器使用新的auto关键字替换了变量声明的类型说明符。一般来说,只要变量声明的类型和它的初始化类型相匹配,这样的替换就可以做。当然,这个转换器只是针对一小部分特殊的方便可读性和可维护性的实用场景:
- 当变量是一个STL容器的迭代器的时候。
std::vector<std::pair<int, std::string> >::iterator NameAgeI = People.begin(); for (std::vector<MyType>::iterator I = Container.begin(), E = Container.end; I != E; ++I) { // ... }
⇒auto NameAgeI = People.begin(); for (auto I = Container.begin(), E = Container.end; I != E; ++I) { // ... }
- 当初始化器是一个使用new操作符的分配空间动作时。
MyType *VarPtr = new MyType(); MyType * const VarCPtr = new MyType();
⇒auto VarPtr = new MyType(); auto const VarCPtr = new MyType();
正在开发支持的第三种情况:使用工厂方法创建对象。
MyType *FooPtr = makeObject<MyType>(/*...*/); MyType *BarPtr = MyType::create(/*...*/);
⇒
auto FooPtr = makeObject<MyType>(/*...*/); auto BarPtr = MyType::create(/*...*/);
在每一种情形之下,为声明产生的类型都应该是读者来说非常明显的。标准容器的迭代器是通过具有特殊名字的函数所创建的,并且使用在特殊的情景之下。对于工厂方法和new操作符来说,类型已经在初始化的时候表明了,所以在变量声明的时候重复说明没有必要。
override虚拟说明符转换器,Philip Dunstan贡献,它是这个迁移器的第四个转换器并且是第一个来自Intel核心小组之外的贡献。这个转换器检测派生来之中的重载父类成员函数的虚成员函数,并且给它们加上override虚拟说明符。
override虚拟说明符转换器,Philip Dunstan贡献,它是这个迁移器的第四个转换器并且是第一个来自Intel核心小组之外的贡献。这个转换器检测派生来之中的重载父类成员函数的虚成员函数,并且给它们加上override虚拟说明符。
class Parent { public: virtual int getNumChildren(); }; class Child { public: virtual int getNumChildren(); };
⇒
class Parent { public: virtual int getNumChildren(); }; class Child { public: virtual int getNumChildren() override; };
更多关于这些转换器的细节,包括它们能做什么、不能做什么,怎么样调整它们的行为,甚至了解他们的局限性,这些都可以在这个文档cpp11-migrate User's Manual中找到。
在真实的工程上测试
还有什么比在真实的工程上运行C++11迁移器进行测试更好的方法么?我们已经建立起了一个持续的合成服务器去构建和运行cpp11-migrate,目前已经运行了两个工程,已经有计划运行至少三个工程。对于每个工程,目标是构建转换的代码并且运行这些工程的测试包,来确保语义没有被改变。
已经实现的:
1. LLVM3.1
2. ITK 4.3.1
计划实现的:
1. LLDB
2. OpenCV
3. Poco
在实际的代码上运行迁移器对发现缺陷很有帮助。各种项目的真实代码经常揭示转换器的实际开发和单元测试无法揭示的代码表达式。每次转换这些工程的缺陷被修复,新的测试用例都会加到回归测试包里,迁移器就会变得更加健壮。
接下来的工作
尽可能快的通过迁移真实的代码去修复缺陷,是目前高优先级的,因为我们想给尽可能多的用户带来很好的用户体验。添加更多的转换器是另外一个优先的任务,同时社区最感兴趣的转换器将会首先被添加。目前列表的最顶端是:
1. 使用标准库去替代TR1
2. 取代使用已经废弃的auto_prt类。
为了修复缺陷和添加转换器,还有一些更普遍的改进需要考虑。其中之一的改进已经正在实现,那就是去除只有源文件可以转换而它所包含的任何头文件都不转换的限制。这个限制直到现在才开始做是因为迁移器需要知道哪些头文件是可以安全转换的。系统头文件和第三方库的头文件很明显是不能碰的。