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

One step closer

拥抱C++11,一步一步来第一篇第二篇之后,develop分支又经过了一次改进。现在,编译KlayGE所需要的编译器提升到了vc11、g++4.6和clang 3.4。相比上一次的vc10和g++4.3这样刚开始支持C++11的编译器来说,11和4.6基本支持了所有的C++11特性。所以代码里面可以比较自由地使用C++11,而代码更简单。

全部支持的特性

目前vc11、g++ 4.6和clang 3.4都支持的C++11特性如下,远多于vc10、g++ 4.3和clang 3.0这个组合。

语言核心部分

  • Static assertions (N1720)
  • Multi-declarator auto (N1737)
  • Right angle brackets (N1757)
  • auto-typed variables (N1984)
  • Extern templates (N1987)
  • Rvalue references (N2118)
  • Declared type of an expression (N2343)
  • Standard Layout Types (N2342)
  • Strongly-typed enums (N2347)
  • Null pointer constant (N2431)
  • New function declarator syntax (N2541)
  • Removal of auto as a storage-class specifier (N2546)
  • Forward declarations for enums (N2764)
  • New wording for C++11 lambdas (N2927)
  • Range-based for (N2930)

库部分

  • algorithm
  • array
  • atomic
  • cstdint
  • functional
  • memory
  • random
  • system_error
  • tuple
  • type_traits
  • unordered_map
  • unordered_set

一些例外

有些特性,其实很早以前各个编译器和它们所带的库就已经支持了,但并非无条件支持。所以这里仍需要把它们列为可选,并在必要的时候退回到boost的实现。

chrono和thread

这两个库需要在编译GCC本身的时候就定义了_GLIBCXX_HAS_GTHREADS才能使用。对于MinGW-w64来说,它的threads-posix版本有个用pthread实现的std::thread,所以定义了_GLIBCXX_HAS_GTHREADS,能使用chrono和thread。而threads-win32版本却不能,得用boost的。Android NDK里面,g++4.6也没有定义_GLIBCXX_HAS_GTHREADS,到4.8才开始有。所以目前保留了退回boost的wrapper。

mem_fn

mem_fn是<functional>的一部分,本来早该支持。但经过测试,在Windows上,g++直到4.8所带的mem_fn才支持stdcall,之前的编译能通过,执行的时候崩溃。clang用的是MinGW的libstdc++,所以也是一样要g++ 4.8以上。而其他平台不用stdcall,没这个问题。所以目前也需要在不支持的时候退回boost。

atomic、date_time和system

atomic和system已经用的是C++11的,但boost的thread依赖于这三个库,所以也只能在编译boost的时候启用它们。

未来

我们已经基本实现了转换到C++11的目标。原计划是把vc的最小需求升级到12,但因为12的普及率还没那么高,而且12增加的C++11特性用到的不多,这个版本就暂时只到11。在下一个版本中,编译器要求会升级到vc12、g++ 4.9和clang 3.4。并且会考虑开始使用C++14的特性。

另外,我还发现一个挺有意思的地方,decltype。decltype的spec通常被分为v1.0v1.1。v1.1修正了v1.0的一个小但重要的bug,在最后一刻才被放入了C++11的标准。vc12的正式版和g++ 4.8.1才支持v1.1,之前都是只有v1.0。

那么它们的区别在什么地方呢?下面的代码:

std::vector<T> v;
...
for (std::vector<T>::const_reference i : v)
{
    ...
}

用decltype来简化,在v1.0里,就需要写成:

std::vector<T> v;
...
typedef decltype(v) v_type;
for (v_type::const_reference i : v)
{
    ...
}

而在v1.1里,可以直接写:

std::vector<T> v;
...
for (decltype(v)::const_reference i : v)
{
    ...
}

这样可以少一个typedef,代码干净得多。这个问题在Boost.Typeof里就出现过。当时只有vc上可以这么用,而g++的原生typeof必须要经过一次typedef。我在向boost提交了个bug之后,他们认为是gcc的bug,让我自己找gcc。但后来有人找到了一个workaround,用boost::mpl::identity或者自己写一个identity包一层,就解决了。没想到这对decltype也管用。首先定义一下:

template <typename T>
struct identity
{
    typedef T value_type;
};

#define KLAYGE_DECLTYPE(x) identity<x>::value_type

接着就可以简化代码了:

std::vector<T> v;
...
for (KLAYGE_DECLTYPE(v)::const_reference i : v)
{
    ...
}

这个诀窍适用于vc10+和g++ 4.3+,但因为到下一次升级编译器要求的时候,decltype v1.1就已经都支持了,我这里就先不弄,以后再说了。