背景
长那么大还没看过开源项目源码,说出去太菜了,所以一直想找个C++开源项目学习一下。正好前阵子公司项目里用到了RocksDB,了解到它是leveldb的升级版,而且两个开源项目都是C++开发的,于是心动了。
本来想看RocksDB的,但看了下相比leveldb它升级了很多东西,源码多了很多,编译出来的静态库足有几百兆……所以退缩了,决定先看基础班leveldb,后面有机会再去了解RocksDB。
然而,看了几天leveldb……也好难啊……慢慢啃……
对于我这个没看过源码不知道阅读源码方法的本菜鸡来说,最好第一个阅读的项目能找到足够的解析文档来帮助学习。谷歌之后从网上找了阿里一位大佬写的《leveldb实现解析》,据说是分析leveldb比较好的文章,于是我也决定借助这篇文档来学习,希望也能学学如何看一个庞大的项目源码。
这是本次levedb系列学习的第一篇笔记,希望能坚持看完。
基本概念
Slice(include/leveldb/slice.h)
这其实就是leveldb中的string类。
leveldb没有使用std::string
,不记得在哪看过,好像Google内部C++项目里都不使用标准库的string,而都是用slice类来代替,不知道是真是假。
slice.h其实就是字符串简单的封装,可以直接操控指针避免不必要的数据拷贝。
Note:感觉面试里让手写一个string类就可以参考这个,不过复制构造函数用的是default,尴尬了。
整体代码并不长,而且一目了然,陈列如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86class LEVELDB_EXPORT Slice {
public:
// Create an empty slice.
Slice() : data_(""), size_(0) {}
// Create a slice that refers to d[0,n-1].
Slice(const char* d, size_t n) : data_(d), size_(n) {}
// Create a slice that refers to the contents of "s"
Slice(const std::string& s) : data_(s.data()), size_(s.size()) {}
// Create a slice that refers to s[0,strlen(s)-1]
Slice(const char* s) : data_(s), size_(strlen(s)) {}
// Intentionally copyable.
Slice(const Slice&) = default;
Slice& operator=(const Slice&) = default;
// Return a pointer to the beginning of the referenced data
const char* data() const { return data_; }
// Return the length (in bytes) of the referenced data
size_t size() const { return size_; }
// Return true iff the length of the referenced data is zero
bool empty() const { return size_ == 0; }
// Return the ith byte in the referenced data.
// REQUIRES: n < size()
char operator[](size_t n) const {
assert(n < size());
return data_[n];
}
// Change this slice to refer to an empty array
void clear() {
data_ = "";
size_ = 0;
}
// Drop the first "n" bytes from this slice.
void remove_prefix(size_t n) {
assert(n <= size());
data_ += n;
size_ -= n;
}
// Return a string that contains the copy of the referenced data.
std::string ToString() const { return std::string(data_, size_); }
// Three-way comparison. Returns value:
// < 0 iff "*this" < "b",
// == 0 iff "*this" == "b",
// > 0 iff "*this" > "b"
int compare(const Slice& b) const;
// Return true iff "x" is a prefix of "*this"
bool starts_with(const Slice& x) const {
return ((size_ >= x.size_) && (memcmp(data_, x.data_, x.size_) == 0));
}
private:
const char* data_;
size_t size_;
};
inline bool operator==(const Slice& x, const Slice& y) {
return ((x.size() == y.size()) &&
(memcmp(x.data(), y.data(), x.size()) == 0));
}
inline bool operator!=(const Slice& x, const Slice& y) { return !(x == y); }
inline int Slice::compare(const Slice& b) const {
const size_t min_len = (size_ < b.size_) ? size_ : b.size_;
int r = memcmp(data_, b.data_, min_len);
if (r == 0) {
if (size_ < b.size_)
r = -1;
else if (size_ > b.size_)
r = +1;
}
return r;
}
}
该文件本身没有什么值得说道的地方,唯一可以注意一下的是重载运算符!=
方法的实现方法是调用重载运算符==
,这种实现方式还是很多的,比如后缀++
的实现中一般会调用前缀++
的函数,然后解引用运算符*
和下表运算符[]
好像也用了类似的实现方法,不太记得了,可以看下stl实现里面。
接下来说一下该文件里类声明中包含的LEVELDB_EXPORT
,该字段是一个宏,定义不在本文件中,而是在include/leveldb/export.h
中,该文件内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
说实话看到这玩意我是真的晕了,虽然大致能明白意思,但看的还是很难受,宏定义真的又强大又麻烦。到这里又不得不说谷歌C++规范里提到的在#endif
或者右括号}
后面跟上对应的注释实在是太必要了,比如上面代码里就在#else
和#endif
后面添加了必要的注释,提高可读性(同样是不记得在哪里见过,预处理宏定义命令都建议顶格写,可能是因为这个原因所以这里没有缩进)。
其实还是第一次见到类似#if defined
的写法,以前最多见过#ifdef
,于是查了下资料,发现其实这两者很相似,唯一区别在于#ifdef
是#if defined
的缩写形式,前者只能判断单个宏是否定义,而后者可以组成更加复杂的预编译条件。
比如#if defined(DEBUG) && VERSION>3
,而用#ifdef
必须这么写:1
2
3
4
5
……
好了,解决了一些基本问题,可以来看最重要的有关LEVELDB_EXPORT
的那几行。
这里涉及到几个之前没有接触过的函数,包括__declspec(dllexport)
、__declspec(dllimport)
以及__attribute__((visibility("default")))
,接下来一一进行简单解释。
函数 | 用处 |
---|---|
__declspec(dllexport) | 将一个函数声明为导出函数,可以省掉在DEF文件中手工定义导出哪些函数。当然如果DLL里都是C++的类的话,就只能导出类。 |
__declspec(dllimport) | 相反,就是将一个函数声明为导入函数,是从其他动态库里引入的函数。一般情况下不使用该函数也能正确编译,但使用该函数可以使编译器生成更好的代码。不过当类中有静态变量的时候,必须要引入该函数,否则会报错。 |
attribute((visibility(“default”))) | 控制共享文件导出符号,default 表示用它修饰的符号将被导出,动态库中的函数默认是可见的。hidden 则意味不可见。其中gcc的visibility是说,如果编译的时候用了这个属性,那么动态库的符号都是hidden的,除非强制声明。 |
综上,主要是为了一些导出符号考虑,暂时先了解一下,感觉就是为了让其他模块能够使用slice类吧,深层次的东西咱也不了解,知道就行。