首页 > 技术文章 > chromium code 中 普遍使用的 C++11 语法

liaokang 2017-03-15 15:10 原文

chromium code:https://chromium.googlesource.com/chromium/src/+/master,windows下采用vs2015,通过 gn gen out\Release --ide=vs 的方式,在out\Release 目录下生成project。

本文结合以下博文内容进行部分说明。

http://www.cnblogs.com/TianFang/archive/2013/01/25/2876099.html

http://www.cnblogs.com/TianFang/p/3163229.html

http://www.cnblogs.com/TianFang/p/3306231.html

自动类型推导auto

现在c++终于在编译器级别支持类似C#的var关键字了,在c++里的关键字auto,基本用法如下:

  1: auto i = 0;             //int
  2: auto c = 'c';           //char
  3: auto s = "hello world"; //const char*
auto关键字的一个很直观的好处是我们可以简化stl容器遍历里的那个iterator了: 
  1: for(auto it = v.begin(); it != v.end(); it++) {
  2:   cout << *it << endl;
  3: }

chromium code

  1: // 比如 base\containers\scoped_ptr_hash_map.h 中
  2: inline void clear() {
  3:   auto it = data_.begin();
  4:   while (it != data_.end()) {
  5:     // NOTE: Deleting behind the iterator. Deleting the value does not always
  6:     // invalidate the iterator, but it may do so if the key is a pointer into
  7:     // the value object.
  8:     auto temp = it;
  9:     ++it;
 10:     // Let ScopedPtr decide how to delete.
 11:     ScopedPtr(temp->second).reset();
 12:   }
 13:   data_.clear();
 14: }

Lambda 表达式

Lambda 表达式的主要用来构造匿名函数,它可以替换绝大部分场景的仿函数(感觉把刚刚引入STL库的std::bind也顺手给秒了),并且有更好的可读性。一个简单的示例如下:

  1: auto k = [](int x, int y) { return x + y; };
  2: cout << k(3, 2) << endl; 

可以看到,它的基本语法形式为: [capture] (parameters) {body},后两部分和C#的匿名函数的语法非常,但最前面的 [capture] 的作用是什么呢?它是用来引用表达式外的局部变量的,例如:

  1: int i1 = 0, i2 = 3;
  2: auto k = [&i1, &i2] () { i1 = 3; i2 = 5; };
  3: cout << i1 << " " << i2 << endl; 

除了是前面所述的普通局部变量外,还可以是如下特殊符号:

  • =    所有局部变量,以按值的方式传递(包括this指针)

  • &    所有局部变量,以按引用的方式传递(包括this指针)

  • this this指针

Range-based for-loop

这个其实就是类似C#里面的foreach,不过它并没有引入新关键字,而是直接用的for

  1: int p[8] = {2, 3, 5, 7, 11, 13, 17, 19};
  2: for (auto& i: p) {
  3:   printf("%d ", i);
  4: } 

除了支持数组外,它也支持stl中的迭代器遍历,for_each函数基本上可以下岗了。至于它的原理,可以参考这篇文章

chromium code

  1: // 比如 base\memory\scoped_vector.h 中
  2: void resize(size_t new_size) {
  3:   if (v_.size() > new_size) {
  4:     for (auto it = v_.begin() + new_size; it != v_.end(); ++it)
  5:       delete *it;
  6:   }
  7:   v_.resize(new_size);
  8: }

枚举类

在C语言中,枚举等同于一个数字常量,编译器并没有对它做过多的处理,在C++11中,引入了enum class以替换枚举,它的定义方式如下:

  1: enum class Color { Red, Blue};
  2: enum class Fruit { Banana, Apple}; 

除了多加了一个class外,没有别的变化,但它定义的枚举功能和C#中的枚举相似。和C语言的传统枚举相比,主要有如下两个区别:

  1. 强类型的语法检查

  2. 枚举名称只需要在class范围内唯一

强类型的语法检查可以有效防止枚举之间的非法转换和比较,Color::Red == Fruit::Banana之类的判断无法编译通过。

而枚举名称只需要在class范围内唯一则有效缩短了枚举的长度,对于Color枚举,在C语言中,为了防止歧义和命名冲突,往往需要定义为Color_Red、Color_Blue的形式。

chromium code

  1: // 比如 base\strings\string_util.h 中
  2: enum class CompareCase {
  3:   SENSITIVE,
  4:   INSENSITIVE_ASCII,
  5: };

静态断言static_assert

静态断言主要提供了在代码中实现编译期可以确定的断言,如果出错则直接以编译错误的形式显示出来,从而加强程序的健壮性。这个其实就是以前的boost.static_assert,不过在编译器的支持下变得更加友好了。示例如下:

  1: static_assert(sizeof(int) == 4, "int needs to be 4 bytes to use this code"); 

chromium code

  1: // 比如 base\numerics\safe_conversions.h 中
  2: // Convenience function for determining if a numeric value is negative without
  3: // throwing compiler warnings on: unsigned(value) < 0.
  4: template <typename T,
  5:           typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
  6: constexpr bool IsValueNegative(T value) {
  7:   static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
  8:   return value < 0;
  9: }

密闭类和密闭方法

C++11中也引入了类似C# seal关键字的方式实现密闭类和密闭方法,以阻止对象继承、方法重载。不过它的关键字是final,和java类似。

final类

  1: class Base final
  2: {
  3: };
  4: class Derived : public Base //继承密闭类,语法错误
  5: {
  6: }; 

final方法

  1: class Base {
  2:   virtual void A() final;
  3: };
  4: class Derived : public Base {
  5:   virtual void A(); //重写密闭方法,编译出错
  6: }; 

显式覆盖

对于虚函数的重写,在c++ 11中可以通过关键字显式覆盖,从而获得更严格的语法检查。

  1: class Base {
  2:   virtual void A(float=0.0);
  3:   virtual void B() const;
  4:   virtual void C();
  5:   void D();
  6: };
  7: class Derived: public Base {
  8:   virtual void A(int=0) override; //定义不一致,编译错误
  9:   virtual void B() override;      //返回类型不是const,编译错误
 10:   virtual void C() override;      //正确
 11:   void D() override;              //不是虚函数,编译错误
 12: }; 

原生字符串(raw string literals)

很多时候,当我们只需要一行字符串的时候,字符串转义往往成了一个负担,和写和读都带了很大的不便。例如,对于如下路径"C:\Program Files\Microsoft.NET\ADOMD.NET",我们必须把它写成如下形式:

  1: string path = "C:\\Program Files\\Microsoft.NET\\ADOMD.NET"; 

可能你会说这个并没有多大影响,下面这个正则表达式的例子呢?你能看出来原文到底是什么吗?

  1: string exp = "('(?:[^\\\\']|\\\\.)*'|\"(?:[^\\\\\"]|\\\\.)*\")|"; 

在C#中,我们可以通过@关键字来取消字符串转义。现在,在C++ 11中,也增加了这样的语法。对于前面的例子,它的非转义形式为:

  1: string path = R"(C:\Program Files\Microsoft.NET\ADOMD.NET)"; 

从上面的例子中可以看出,它的语法格式如下:

  1. 字符串前加'R'前缀

  2. 字符串首尾加上括号()

它的语法格式比C#的@前缀要稍微复杂点,不过这个复杂也有复杂的好处,那就是字符串里面可以带引号,例如:

  1: string path = R"(this "word" is escaped)"; 

而C#就无法保持原始字符串格式,对引号仍需要转义:

  1: string path = @"this ""word"" is escaped"; 

chromium code

  1: // 比如 tools\gn\command_ls.cc 中
  2: 
  3: #define TARGET_PRINTING_MODE_COMMAND_LINE_HELP \
  4:     "  --as=(buildfile|label|output)\n"\
  5:     "      How to print targets.\n"\
  6:     "\n"\
  7:     "      buildfile\n"\
  8:     "          Prints the build files where the given target was declared as\n"\
  9:     "          file names.\n"\
 10:     "      label  (default)\n"\
 11:     "          Prints the label of the target.\n"\
 12:     "      output\n"\
 13:     "          Prints the first output file for the target relative to the\n"\
 14:     "          root build directory.\n"
 15: 
 16: #define ALL_TOOLCHAINS_SWITCH_HELP \
 17:   "  --all-toolchains\n" \
 18:   "      Normally only inputs in the default toolchain will be included.\n" \
 19:   "      This switch will turn on matching all toolchains.\n" \
 20:   "\n" \
 21:   "      For example, a file is in a target might be compiled twice:\n" \
 22:   "      once in the default toolchain and once in a secondary one. Without\n" \
 23:   "      this flag, only the default toolchain one will be matched by\n" \
 24:   "      wildcards. With this flag, both will be matched.\n"
 25: 
 26: 
 27: #define TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP \
 28:     "  --testonly=(true|false)\n"\
 29:     "      Restrict outputs to targets with the testonly flag set\n"\
 30:     "      accordingly. When unspecified, the target's testonly flags are\n"\
 31:     "      ignored.\n"
 32: 
 33: #define TARGET_TYPE_FILTER_COMMAND_LINE_HELP \
 34:     "  --type=(action|copy|executable|group|loadable_module|shared_library|\n"\
 35:     "          source_set|static_library)\n"\
 36:     "      Restrict outputs to targets matching the given type. If\n"\
 37:     "      unspecified, no filtering will be performed.\n"
 38: 
 39: // 可以看到kLs_Help为两个R"()" 和多个"" 的组合
 40: const char kLs_Help[] =
 41:     R"(gn ls <out_dir> [<label_pattern>] [--all-toolchains] [--as=...]
 42:       [--type=...] [--testonly=...]
 43: 
 44:   Lists all targets matching the given pattern for the given build directory.
 45:   By default, only targets in the default toolchain will be matched unless a
 46:   toolchain is explicitly supplied.
 47: 
 48:   If the label pattern is unspecified, list all targets. The label pattern is
 49:   not a general regular expression (see "gn help label_pattern"). If you need
 50:   more complex expressions, pipe the result through grep.
 51: 
 52: Options
 53: 
 54: )"
 55:     TARGET_PRINTING_MODE_COMMAND_LINE_HELP
 56: "\n"
 57:     ALL_TOOLCHAINS_SWITCH_HELP
 58: "\n"
 59:     TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP
 60: "\n"
 61:     TARGET_TYPE_FILTER_COMMAND_LINE_HELP
 62: R"(
 63: Examples
 64: 
 65:   gn ls out/Debug
 66:       Lists all targets in the default toolchain.
 67: 
 68:   gn ls out/Debug "//base/*"
 69:       Lists all targets in the directory base and all subdirectories.
 70: 
 71:   gn ls out/Debug "//base:*"
 72:       Lists all targets defined in //base/BUILD.gn.
 73: 
 74:   gn ls out/Debug //base --as=output
 75:       Lists the build output file for //base:base
 76: 
 77:   gn ls out/Debug --type=executable
 78:       Lists all executables produced by the build.
 79: 
 80:   gn ls out/Debug "//base/*" --as=output | xargs ninja -C out/Debug
 81:       Builds all targets in //base and all subdirectories.
 82: 
 83:   gn ls out/Debug //base --all-toolchains
 84:       Lists all variants of the target //base:base (it may be referenced
 85:       in multiple toolchains).
 86: )";

委托构造函数(Delegating constructors)

C++的构造是不能复用的,为了复用其初始化操作,我们往往会增加一个Initial函数:

  1: class Foo {
  2: private:
  3:   int A;
  4: public:
  5:   Foo() : A(0) {
  6:     Init();
  7:   }
  8:   Foo(int a) : A(a) {
  9:     Init();
 10:   }
 11: private:
 12:   void Init() {
 13:     // do something
 14:   }
 15: }; 

这样一来就增加了一个只调用一次的Init函数,并且一旦这个Init函数被其它成员函数调用的话,可能导致重复初始化,也是一个隐患。PS:本例比较简单,通过构造函数默认参数也可以解决构造函数复用问题,但默认参数也有一些局限和带来一些问题,限于篇幅就不做更多的讨论了。

在C++ 11中,引入了委托构造函数的语法,其功能和C#中的this构造函数非常类似,就是语法上稍有差异:

  1: class Foo {
  2: private:
  3:   int A;
  4: public:
  5:   Foo() : Foo(0) {
  6:   }
  7:   Foo(int a) : A(a) {
  8:     // do something
  9:   }
 10: }; 

初始化列表(initializer list)

在C++ 03中,可以用列表的形式来初始化数组,这种方式非常直观,但只能适用于数组,不能适用于我们自定义的容器:

  1: int anArray[5] = { 3, 2, 7, 5, 8 };          // ok 
  2: std::vector<int> vArray = { 3, 2, 7, 5, 8 }; // not ok 

在C++ 11中,我们则可以使得我们自定义的容器对象支持这种列表的形式的初始化方式:

  1: template <typename T>
  2: class MyArray {
  3: private:
  4:   vector<T> m_Array;
  5: public:
  6:   MyArray() { }
  7:   MyArray(const initializer_list<T>& il) {
  8:     for (auto x : il)
  9:       m_Array.push_back(x);
 10:   }
 11: };
 12: void main() {
 13:   MyArray<int> foo = { 3, 4, 6, 9 };
 14: } 

统一初始化(Uniform initialization)

C++的对象初始化方式是非常多样的:

  1: int a = 2;          //"赋值风格"的初始化
  2: int aa [] = { 2, 3 };   //用初始化列表进行的赋值风格的初始化
  3: complex z(1, 2);      //"函数风格"的初始化 

C++ 11中,允许通过以花括号的形式来调用构造函数。这样多种对象构造方式便可以统一起来了:

  1: int a = { 2 };
  2: int aa [] = { 2, 3 };
  3: complex z = { 1, 2 };

值得一提的是,这种花括号的构造方式还可以用于函数的参数和返回值的类型推导,非常简洁。

  1: void useMyStruct(MyStruct x) { }
  2: useMyStruct({ 2, 3.5f });
  3: MyStruct makeMyStruct(void)  {
  4:   return { 2, 3.5f };
  5: }

非静态成员直接初始化

在C++ 03的时候,非静态成员变量只能在对象的构造函数里初始化,例如:

  1: struct A {
  2:   int m;
  3:   A() : m (7) { }
  4: };

当对象成员比较多的时候,这个对象成员的初始化是非常难看的。尤其是在构造函数较多的情况下,由于C++ 03不支持委托构造函数,这一大堆的成员需要在每一个构造函数中初始化一遍,是十分冗繁而容易出错的。

在C++ 11中,非静态成员也能以静态成员那种方式直接初始化的,这样就直观的多了:

  1: struct A {
  2:   int m = 7;
  3: };

chromium code

 

启用和禁止默认函数

在C++中,编译器会生成一些默认函数,例如对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A产生四个缺省的函数,如

  1: A(void);                      // 缺省的无参数构造函数
  2: A(const A &a);                // 缺省的拷贝构造函数
  3: ~A(void);                     // 缺省的析构函数
  4: A & operate =(const A &a);    // 缺省的赋值函数

在C++ 11中,支持通过 default 和 delete 两个关键字来管理这默认函数。delete意为禁止默认函数,default则使用默认函数。

例如,当我们自定义你空构造函数时,编译器就不会给我们生成缺省无参构造函数,此时则可以通过= default来让编译器产生默认构造函数。

  1: struct A {
  2:   A() = default;
  3:   A(int n) {}
  4: };

至于= delete,一个比较典型的用途是可以定义一个不可拷贝的对象,如:

  1: struct NoCopy {
  2:   NoCopy & operator =(const NoCopy &) = delete;
  3:   NoCopy(const NoCopy &) = delete;
  4: };

另外,= delete也可以用于禁止那些隐式转换而执行的函数,例如:

  1: struct A {
  2:   void foo(int k){}
  3:   void foo(double k) = delete;
  4: };

这样,类似a.foo(3.2)之类的调用就会有编译错误。

chromium code

1: // 比如 base\macros.h 中 定义宏

  2: // Put this in the declarations for a class to be uncopyable.
  3: #define DISALLOW_COPY(TypeName) \
  4:   TypeName(const TypeName&) = delete
  5: 
  6: // Put this in the declarations for a class to be unassignable.
  7: #define DISALLOW_ASSIGN(TypeName) \
  8:   void operator=(const TypeName&) = delete
  9: 
 10: // A macro to disallow the copy constructor and operator= functions.
 11: // This should be used in the private: declarations for a class.
 12: #define DISALLOW_COPY_AND_ASSIGN(TypeName) \
 13:   TypeName(const TypeName&) = delete;      \
 14:   void operator=(const TypeName&) = delete
 15: 
 16: // A macro to disallow all the implicit constructors, namely the
 17: // default constructor, copy constructor and operator= functions.
 18: //
 19: // This should be used in the private: declarations for a class
 20: // that wants to prevent anyone from instantiating it. This is
 21: // especially useful for classes containing only static methods.
 22: #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
 23:   TypeName() = delete;                           \
 24:   DISALLOW_COPY_AND_ASSIGN(TypeName)
 25: 
 26: // 比如 base\memory\weak_ptr.h 中
 27: template <class T>
 28: class WeakPtrFactory {
 29:  public:
 30:   explicit WeakPtrFactory(T* ptr) : ptr_(ptr) {
 31:   }
 32: 
 33:   ~WeakPtrFactory() { ptr_ = nullptr; }
 34: 
 35:   WeakPtr<T> GetWeakPtr() {
 36:     DCHECK(ptr_);
 37:     return WeakPtr<T>(weak_reference_owner_.GetRef(), ptr_);
 38:   }
 39: 
 40:   // Call this method to invalidate all existing weak pointers.
 41:   void InvalidateWeakPtrs() {
 42:     DCHECK(ptr_);
 43:     weak_reference_owner_.Invalidate();
 44:   }
 45: 
 46:   // Call this method to determine if any weak pointers exist.
 47:   bool HasWeakPtrs() const {
 48:     DCHECK(ptr_);
 49:     return weak_reference_owner_.HasRefs();
 50:   }
 51: 
 52:  private:
 53:   internal::WeakReferenceOwner weak_reference_owner_;
 54:   T* ptr_;
 55:   DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory);
 56: };

类型别名

虽然自C语言时代就支持通过typedef来为类型起别名,但typedef对函数指针起别名显得比较难看,并且不支持模板。因此,在C++ 11种新增了using为对象起别名的语法:

  1: // typedef std::ios_base::fmtflags flags;
  2: using flags = std::ios_base::fmtflags; 
  3: 
  4: // typedef void (*func)(int, int);
  5: using func = void(*) (int, int);
  6: 
  7: template<class T> using ptr = T*;
  8: //'ptr<T>' 等价于 T 的指针
  9: ptr<int> x;

chromium code

  1: // 比如 base\bind_internal.h 中
  2: template <typename R, typename... Args>
  3: struct ForceVoidReturn<R(Args...)> {
  4:   using RunType = void(Args...);
  5: };

推荐阅读