Quantcast
Channel: 程序人生 »代码疯子
Viewing all articles
Browse latest Browse all 59

Chrome源码阅读之basictypes.h

$
0
0

对Chrome的源码阅读主要集中在src\base下,这里面实现了很多基础组件,相对来说更容易理解,也是阅读其他源码的一个基础。

文件src\base\basictypes.h定义了一些基础数据结构和宏。

1. 明确拒绝不需要的函数
下面这部分代码用于禁用类不需要的复制构造函数、复制操作符以及隐式的构造函数。

#define DISALLOW_COPY(TypeName) \
  TypeName(const TypeName&)
 
#define DISALLOW_ASSIGN(TypeName) \
  void operator=(const TypeName&)
 
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
  TypeName(const TypeName&);               \
  void operator=(const TypeName&)
 
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
  TypeName();                                    \
  DISALLOW_COPY_AND_ASSIGN(TypeName)

2. 编译期间条件检查
下面这部分代码用于编译期间的条件检查,如果COMPILE_ASSERT(expr, msg)中expr不为true,就会定义一个名字为msg,大小为-1的数组,显然编译器不允许定义一个大小小于零的数组,从而中断编译操作。

template <bool>
struct CompileAssert {
};
 
#undef COMPILE_ASSERT
#define COMPILE_ASSERT(expr, msg) \
  typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1]
 
// 使用示例
COMPILE_ASSERT(sizeof(foo) < 128, foo_too_large);

3. ArraySizeHelper求数组大小
basictypes.h文件中有如下一段定义:

template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
 
#define arraysize(array) (sizeof(ArraySizeHelper(array)))

先说一下数组的引用。看一段代码:

#include "stdafx.h"
#include <vector>
#include <string>
#include <iostream>
using namespace std;
 
// 定义一个返回数组引用的函数,其中param为函数参数
int (&test(string param))[3]
{
	cout << "param is " << param << endl;
	static int a3[] = {1, 2, 3};
	return a3;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
	int a3[] = {1, 2, 3};
	int (&(a))[3] = a3;		// 定义一个数组的引用
	for (int i = 0; i < 3; ++i)
	{
		cout << a[i] << endl;	// 输出数组元素
	}
	cout << sizeof(a) << endl;	// 相当于sizeof(a3)
	cout << sizeof(test("test")) << endl;
 
	return 0;
}

这里面提到了数组引用的定义,以及返回数组引用的函数的写法。回到Chrome的代码,定义了一个名为ArraySizeHelper的模板函数,函数的参数为类型为T元素个数为N的数组的引用,返回值为类型为char元素个数也为N的数组的引用,这样sizeof刚好得到元素个数,因为char占用1个字节,且参数array只能是数组,整个过程全部在编译期间完成。ArraySizeHelper本身不需要定义,因为sizeof只需要知道函数的返回类型即可。

4. 类型转换
在Chrome中有些地方会经常遇到reinterpret_cast,比如注册表读写的时候,数据参数是BYTE指针类型,那么需要进行转型操作,老式的C语言转型操作会带来很多问题(比如语法意义不明确等),reinterpret_cast操作符可以在不改变实际数据的情况下改变数据的类型,很适用于下面的场景:

LONG RegKey::WriteValue(const wchar_t* name,
                        const void* data,
                        DWORD dsize,
                        DWORD dtype) {
  DCHECK(data || !dsize);
 
  LONG result = RegSetValueEx(key_, name, 0, dtype,
      reinterpret_cast<LPBYTE>(const_cast<void*>(data)), dsize);
  return result;
}

basictypes.h文件中新增了两种转型包装:bit_cast和implicit_cast。

template <class Dest, class Source>
inline Dest bit_cast(const Source& source) {
  // Compile time assertion: sizeof(Dest) == sizeof(Source)
  // A compile error here means your Dest and Source have different sizes.
  typedef char VerifySizesAreEqual [sizeof(Dest) == sizeof(Source) ? 1 : -1];
 
  Dest dest;
  memcpy(&dest, &source, sizeof(dest));
  return dest;
  // 因为是内联函数,代码会展开,所以这里“返回”局部变量不会有问题
}
 
//   // WRONG
//   float f = 3.14159265358979;            // WRONG
//   int i = * reinterpret_cast<int*>(&f);  // WRONG
//
// The address-casting method actually produces undefined behavior
// according to ISO C++ specification section 3.10 -15 -.  Roughly, this
// section says: if an object in memory has one type, and a program
// accesses it with a different type, then the result is undefined
// behavior for most values of "different type".
//
// This is true for any cast syntax, either *(int*)&f or
// *reinterpret_cast<int*>(&f).  And it is particularly true for
// conversions between integral lvalues and floating-point lvalues.

bit_cast实现了类似于*reinterpret_cast(&source)的功能,按照代码片段中的注释的说法,上面使用reinterpret_cast的方式可能引发未定义的操作。另外提到bit_cast的参数类型如果不是POD,那么会可能会出问题。

维基百科上是这么解释POD(Plain Old Data)的:

POD类类型就是指class、struct、union,且不具有用户定义的构造函数、析构函数、
拷贝算子、赋值算子;不具有继承关系,因此没有基类;不具有虚函数,所以就没有虚表;
非静态数据成员没有私有或保护属性的、没有引用类型的、没有非POD类类型的
(即嵌套类都必须是POD)、没有指针到成员类型的(因为这个类型内含了this指针)。

比如对于包含虚函数的类对象,显然不适合使用bit_cast。

template<typename To, typename From>
inline To implicit_cast(From const &f) {
  return f;
}

implicit_cast作为static_cast和const_cast的安全版本。

5. 静态类对象
basictypes.h中有这样一段代码以及注释:

// The following enum should be used only as a constructor argument to indicate
// that the variable has static storage class, and that the constructor should
// do nothing to its state.  It indicates to the reader that it is legal to
// declare a static instance of the class, provided the constructor is given
// the base::LINKER_INITIALIZED argument.  Normally, it is unsafe to declare a
// static variable that has a constructor or a destructor because invocation
// order is undefined.  However, IF the type can be initialized by filling with
// zeroes (which the loader does for static variables), AND the destructor also
// does nothing to the storage, AND there are no virtual methods, then a
// constructor declared as
//       explicit MyClass(base::LinkerInitialized x) {}
// and invoked as
//       static MyClass my_variable_name(base::LINKER_INITIALIZED);
namespace base {
enum LinkerInitialized { LINKER_INITIALIZED };

LINKER_INITIALIZED 用于初始化静态的类对象。对象的类应该是上面提到的POD,如果不是可能会引发问题。在Google C++编程规范中提到:

Static or global variables of class type are forbidden: they cause hard-to-find 
bugs due to indeterminate order of construction and destruction.
Objects with static storage duration, including global variables, static variables, 
static class member variables, and function static variables, must be Plain Old Data (POD): 
only ints, chars, floats, or pointers, or arrays/structs of POD.
 
The order in which class constructors and initializers for static variables are 
called is only partially specified in C++ and can even change from build to build, 
which can cause bugs that are difficult to find. Therefore in addition to banning 
globals of class type, we do not allow static POD variables to be initialized with 
the result of a function, unless that function (such as getenv(), or getpid()) 
does not itself depend on any other globals.
 
Likewise, the order in which destructors are called is defined to be the reverse 
of the order in which the constructors were called. Since constructor order is
indeterminate, so is destructor order. For example, at program-end time a static 
variable might have been destroyed, but code still running -- perhaps in another 
thread -- tries to access it and fails. Or the destructor for a static 'string' 
variable might be run prior to the destructor for another variable that contains 
a reference to that string.
 
As a result we only allow static variables to contain POD data. This rule 
completely disallows vector (use C arrays instead), or string (use const char []).
 
If you need a static or global variable of a class type, consider initializing 
a pointer (which will never be freed), from either your main() function or from 
pthread_once(). Note that this must be a raw pointer, not a "smart" pointer, 
since the smart pointer's destructor will have the order-of-destructor issue 
that we are trying to avoid.

意思是说,对于静态的类对象,C++没有明确规定构造函数和析构函数的调用顺序,也就是你在使用这个静态对象的时候可能还没有初始化或者已经析构了。而对于POD类型,就是用了上面填充0的方式进行初始化,且不存在析构一说。

关于这一点,网上的资料不是很多,也说得不是很清楚。

6. 参考资料
在c++中使用指向数组的引用
POD (程序设计)
Google C++ Style Guide \ Static and Global Variables


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> Chrome源码阅读之basictypes.h
作者:代码疯子


Viewing all articles
Browse latest Browse all 59

Trending Articles