leveldb提供了迭代器遍历的功能,并提供了迭代器的接口。这也是一个纯虚类的接口,leveldb中任何集合类型对象的迭代器都基于这个纯虚类实现。

class Iterator {
public:
    Iterator();
    virtual ~Iterator();
 
    Iterator(const Iterator&) = delete;
    Iterator& operator=(const Iterator&) = delete;
 
    virtual bool Valid() const = 0;
    virtual void SeekToFirst() = 0;
    virtual void SeekToLast() = 0;
    virtual void Seek(const Slice& target) = 0;
    virtual void Next() = 0;
    virtual void Prev() = 0;
    virtual Slice key() const = 0;
    virtual Slice value() const = 0;
    virtual Status status() const = 0;
};

不过因为迭代器接口的实现类也是一个对象,在对象结束的时候也需要使用一些资源,对象的资源也需要释放。为了让简化用户的任务量,将这个资源的释放在这个父类的析构函数实现,提供了注册函数,只要实现者将这些函数注册,那么就能够在对象生命周期结束的时候自动释放,而不需要开发者耗费精力何时去释放内存。

using CleanupFunction = void(*)(void* arg1, void* arg2);
void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);

内部由一个链表,记录着这些清理函数,对象释放的时候执行这些清理函数。

class Iterator {
....
private:
	struct CleanupNode {
		bool IsEmpty() const { return function == nullptr; }
		void Run() {
			assert(function != nullptr);
			(*function)(arg1, arg2);
		}
	
		CleanupFunction function;
		void* arg1;
		void* arg2;
	
		CleanupNode* next;
	
	};
	CleanupNode cleanup_head_;
}

在析构函数中调用这些清理函数:

Iterator::~Iterator() {
	// cleanup_head_是预留的一个头节点,通过它是否设置了清理函数来判断是否需要进行清理
	// 头节点后面的节点如果存在那么肯定是存在清理函数的,执行并删除这个节点
    if (!cleanup_head_.IsEmpty()) {
        cleanup_head_.Run();
        for (CleanupNode* node = cleanup_head_.next; node != nullptr;) {
            node->Run();
            CleanupNode* next_node = node->next;
            delete node;
            node = next_node;
        }
    }
}

清理函数的注册如下:

void Iterator::RegisterCleanup(CleanupFunction function, void* arg1, void* arg2) {
	// 保证注册的清理函数一定存在
    assert(function != nullptr);
    CleanupNode* node;
    // 第一个清理函数可以利用头节点,后面的利用带头节点的头插法来进行链表的插入
    if (cleanup_head_.IsEmpty()) {
        node = &cleanup_head_;
    } else {
        node = new CleanupNode;
        node->next = cleanup_head_.next;
        cleanup_head_.next = node;
    }
    node->function = function;
    node->arg1 = arg1;
    node->arg2 = arg2;
}

这种设计既高度抽象,有提高了代码的复用,非常值得学习。