转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=3350

在上一篇拥抱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。并把它们都支持的特性变成可以直接使用。争取在那时候进一步简化代码,方便开发和使用。