在上一篇拥抱C++11,一步一步来里,我制订了一个一步步使用C++11的路线图。KlayGE 4.7去掉了早于vc10、g++ 4.3和clang 3.0的编译器支持,但由于时间关系,还没来得及把它们都支持的C++11特性改为默认开启,仍然用了wrapper层。现在,KlayGE 4.8的开发已经开始,也有足够的时间真正地来执行C++11的计划。目前已经在develop分支里做实现了一部分。
全都支持的特性
整理一下vc10、g++ 4.3和clang 3.0都支持的C++11特性,可以得到一个列表:
语言核心部分
- Static assertions (N1720)
- Right angle brackets (N1757)
- Extern templates (N1987)
- auto-typed variables (N1984)
- Rvalue references (N2118)
- Declared type of an expression (N2343)
库部分
- array
- cstdint
- functional
- random
- tuple
- type_traits
- unordered_map
- unordered_set
既然都支持,这些特性就不再需要wrapper而可以直接使用了。
部分支持的特性
当然,还有一些特性,支持都没这么高,仍然需要wrapper。比如chrono,在vc11以及定义了_GLIBCXX_HAS_GTHREADS的g++里才支持。以前的wrapper是写成这样的:
#ifdef KLAYGE_CXX11_LIBRARY_CHRONO_SUPPORT #include <chrono> namespace KlayGE { namespace chrono = std::chrono; } #else #include <boost/chrono.hpp> namespace KlayGE { namespace chrono = boost::chrono; } #endif
这就在编译器和原生库支持chrono的时候使用std的版本,否则使用Boost.Chrono。他们都被包到了namespace KlayGE里。但这么一来,上层代码就得使用KlayGE::chrono。类似的情况多了的话,上层代码最终需要迁移到C++11的时候就会需要大量修改。在develop分支里,wrapper不再把东西包到namespace KlayGE,而是包入namespace std。这样以后的新代码就不再需要二次修改,直接就和使用C++11一致了。
#ifdef KLAYGE_CXX11_LIBRARY_CHRONO_SUPPORT #include <chrono> #else #include <boost/chrono.hpp> namespace std { namespace chrono = boost::chrono; } #endif
绝大部分特性都能很好地用这种方式解决,除了个别例外,比如mem_fn。在KlayGE里,mem_fn需要能配合COM接口使用,也就是需要能调用stdcall的成员函数。boost的mem_fn需要通过BOOST_MEM_FN_ENABLE_STDCALL打开这个功能。在vc9、10的mem_fn来自tr1,默认就支持stdcall,但为了简化起见我们不用tr1。vc11的mem_fn经过重写,反而不支持了。我报过一个bug后,在vc12里再次能用stdcall。在这种情况下,如果把mem_fn包到std,编译器就会出错,告诉你已经有个std::mem_fn了。所以这时候只能保留包入namespace KlayGE的代码。将来errc也会遇到这个问题,因为vc11不支持strongly typed enum,它的errc是用namespace实现的。这里也会要用到namespace KlayGE。
下一步
经过这些修改,引擎代码在很大程度上变得更简单了,调试的时候也不会经常进到宏里面制造麻烦。另外编译速度也有略微提高,应该主要是来自预编译的速度提升。
按照计划,在4.8的开发过程中,编译器的要求会再次提高到vc12、g++ 4.6和clang 3.4。并把它们都支持的特性变成可以直接使用。争取在那时候进一步简化代码,方便开发和使用。
Comments