Chromium base库介绍

因为工作原因需要引入base库,我作为新人则挑起了这个活,拿出工作中的一些时间来学习base库,主要关注其实现原理,使用方法,注意事项,特点,优缺点。

本文持续更新
2016/10/8 第一次更新

  • AtExitManager 类似于linux下的atexit,注册退出清理函数,不过base库的实现机制是利用了C++的RAII

void exit_first(void *data) { LOG(INFO) << "first"; }

void exit_last(void *data) { LOG(INFO) << "second"; }

void exit_task(void *data) { LOG(INFO) << "use bind"; }

class task {
 public:
  void func() { LOG(INFO) << "task::func"; }
};

int main() {
  base::AtExitManager manager; //这个对象在析构的时候,会去回调注册的callback
  task tsk;
  base::AtExitManager::RegisterCallback(&exit_first, nullptr);   //注册callback
  base::AtExitManager::RegisterCallback(&exit_last, nullptr);
  base::AtExitManager::RegisterTask(base::Bind(&exit_task, nullptr)); //指出注册base::Bind绑定的callback
  base::AtExitManager::RegisterTask(
      base::Bind(&task::func, base::Unretained(&tsk)));

  // This method can take the initiative to register callback functions
  base::AtExitManager::ProcessCallbacksNow(); //可以自己主动的调用

  return 0;
}
  • atomicops 对C++11的std::atomic的封装,默认情况下C++11的std::atomicSC(sequence consistent)顺序一致性模型的,也就是内部做了Barrier(有性能损耗),但是大多数场景下我们只是想使用atomic特性,因此base针对不同的内存模型进行了封装,有NoBarrierBarrieraccquirerelease等等。

  • AutoReset 给全局变量提供默认值和新值,等AutoReset析构了,就会恢复默认值

  • base64base64url 提供了对url场景和一般场景下的base64编码,具体可以参考RFC-4648

  • BigEndian 一个大端序的bytesbuf。

  • GetBuildTime 通过在build的时候,生成一个带有编译时间的常量的头文件,可以通过这个头文件得到二进制程序的编译时间。

  • Bind/CallBackstd::bind&std::function类似,Google提供了使用文档和设计文档,说明了自己为何没有使用std标准库,而是自己造轮子的原因callback

  • bits 提供了对字节对齐大小的计算,还有计算一个数是2的几次方相关的函数。

  • bind_helpers 类似于std::ref,默认的std::bind是将参数拷贝后再绑定,可以使用std::ref通过传引用的方式绑定,bind_helper提供了更丰富的传参方式,传引用,传值,所有权转移等。

  • BarrierClosure 类似于java的CountDownLatch,需要传一个次数和callback函数,每调用一次次数就减1,直到最后一次才会真正的调用callback函数。

  • Reversed 可以O(1)复杂度翻转容器中的元素,本质上并不是真正的翻转,只是改变了begin()和end()的含义,该函数返回一个迭代器类型,只不过其begin()返回的是容器的rbegin()end()返回的是容器的rend()

  • hash_table/hash_set 默认的std::map std::set底层是红黑树实现,增删改查O(logN),但是有序。这两个类是封装了C++11中引入的unorderd_map/unordered_set,底层是hash表实现增删改查O(1),但是无序。

template <class Key,
          class T,
          class Hash = BASE_HASH_NAMESPACE::hash<Key>,
          class Pred = std::equal_to<Key>,
          class Alloc = std::allocator<std::pair<const Key, T>>>
using hash_map = std::unordered_map<Key, T, Hash, Pred, Alloc>;    //看到这里你应该明白了

template <class Key,
          class Hash = BASE_HASH_NAMESPACE::hash<Key>,
          class Pred = std::equal_to<Key>,
          class Alloc = std::allocator<Key>>
using hash_set = std::unordered_set<Key, Hash, Pred, Alloc>;      //用法和unordered_* 一抹一样
  • LinkedList 链表,比std::list性能更好,删除一个元素O(1),而std::list需要O(n)(C++11中已经支持O(1)删除一个元素了),插入元素的时候没有堆内存分配的操作,std::list则需要分配next指针,而LinkedList内部已经包含了nextprev指针,没有额外的堆内存分配,其用法如下:
//任何想作为LinkedList的Node的,都需要继承这个类,就跟内核中的单链表一样,需要把`struct list`作为其节点的内部成员一样

class Node : public base::LinkNode<Node> {
 public:
  explicit Node(int id) : id_(id) {}
  int id() const { return id_; }

 private:
  int id_;
};

int main() {
  base::LinkedList<Node> list;   //实例化一个链表,节点类型为Node
  Node n1(1);
  list.Append(&n1);

  Node n2(2);
  n2.InsertBefore(&n1);

  Node n3(3);
  list.Append(&n3);

  if (!list.empty()) {
    for (const base::LinkNode<Node>* node = list.head(); node != list.end();
         node = node->next()) {
      LOG(INFO) << node->value()->id();  // OUTPUT 2 1 3
    }
  }
  • MRUCache 内部使用了std::map实现的LRU Cache模板

  • StringPiece C++里面有string和char*,如果你用const string &s 做函数形参,可以同时兼容两种字符串。但当你传入一个很长的char * 时,会生成一个较大的string对象,开销比较大。 如果你的目的仅仅是读取字符串的值,用这个StringPiece的话,仅仅是4+一个指针的内存开销,而且也保证了兼容性。所以这个类的目的是传入字符串的字面值,它内部的ptr_ 这块内存不归他所有。所以不能做任何改动。

  • CallbackList 通过Add方法可以注册多个callback,内部使用了std::list保存了这些callback,Add会返回一个Subscription子类,这个子类析构后会自动安全的释放callback对象从内部的list中移除,并且不会导致迭代器失效。CallbackList的Notify可以传递参数给所有的callback并立即运行。

  • CancelableClosure 可以取消的Callback,

  • MD5 用来对指定字符串创建md5,有多种用法。


int main() {
  //  Case1 直接使用MD5Sum给字符串创建md5,并把结果保存在digest中
  base::MD5Digest digest;
  const char data[] = "testmd5";
  base::MD5Sum(data,strlen(data),&digest);

  //  Case2 通过Update把结果临时保存在MD5Context中,你可以多次Update,结果是累加的,这种用法特别适合数据有多段的场景,无法一次性计算。
  base::MD5Context ctx;
  base::MD5Digest digest2;
  base::MD5Init(&ctx);
  base::MD5Update(&ctx,base::StringPiece(data,strlen(data)));
  base::MD5Final(&digest2,&ctx);

  // 生成的md5最后都是放在digest中的a成员中,这是一个字节数组,直接打印的话会有很多不可见字符
  // 需要转换为字符串
  for(int i = 0;i < 16; ++i) {
    if (digest2.a[i] != digest.a[i])
      LOG(INFO) << "md5 failure";
  }


  //  Case3 直接对一段字符生成md5的字符串形式
  LOG(INFO) << base::MD5String("testmd5");

  // Case4  可以对MD5Digest生成字符串形式
  LOG(INFO) << base::MD5DigestToBase16(digest);
  LOG(INFO) << base::MD5DigestToBase16(digest2);

}
  • sha1效验算法,参考fips180,其用法如下:
int main() {
  // Case 1
  std::string has = "test";
  std::string output = base::SHA1HashString(has);

  const char data[] = "test";

  unsigned char output2[base::kSHA1Length];

  base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(data),
                      strlen(data), output2);

  for (size_t i = 0; i < base::kSHA1Length; i++) {
    if(output2[i] != (output[i] & 0XFF)) {
      LOG(INFO) << "failure";
    }
  }
}
  • ScopedVectorstd::vector<scoped_ptr<T> > 类似,不过因为scoped_ptr在赋值的时候,所有权会转移,导致类似于下面这样的操作会存在问题:
std::vector<scoped_ptr<int> > vec;
vec.push_back<scoped_ptr<int>(new int(1));
scoped_ptr<int> t = vec[0];  // 所有权转移到t了,vector中放到的值变成了未定义的了。

但是如果使用ScopedVector则不会出现这样的问题,可以从中拿到原始指针,代码如下:

int main() {

  ScopedVector<int> vec;
  vec.push_back(new int(3));
  vec.push_back(new int(4));
  vec.push_back(new int(5));

  int *p = vec[0];  //可以拿到原始指针
  *p = 10;

  for(auto num : vec) {
    LOG(INFO) << *num;
  }
}

注: boost::scoped_ptr不可拷贝不可移动,base::scoped_ptr和C++11的unique_ptr类似,base库已经全部替换为unique_ptr了,这个智能指针是可以进行所有权转移。目前ScopedPtr已经被废弃,可以直接使用std::vector<unique_ptr<T>>代替。

  • Watchdog 主要目的用于周期性的debug用途,可以通过继承这个类,实现其虚方法Alarm,使用的时候通过传入一个时间,然后调用Arm方法开始计时,在这个时间范围内如果没有重置时间,那么会导致Alarm方法的调用(内部是单独启了一个线程去异步调用Alarm方法)。
class WatchdogCounter : public base::Watchdog {
 public:
  WatchdogCounter(const base::TimeDelta& duration,
                  const std::string& thread_watched_name,
                  bool enabled)
      : Watchdog(duration, thread_watched_name, enabled),
        alarm_counter_(0) {
  }

  ~WatchdogCounter() override {}

  void Alarm() override {  //需要重载这个方法,里面实现具体的debug逻辑,比如可以打印堆栈啊
    alarm_counter_++;
    Watchdog::Alarm();
  }

  int alarm_counter() { return alarm_counter_; }

 private:
  int alarm_counter_;
  DISALLOW_COPY_AND_ASSIGN(WatchdogCounter);
};


int main() {
  WatchdogCounter watchdog(base::TimeDelta::FromMilliseconds(500), "Enabled", true);
  watchdog.Arm();  //开始计时,通过睡眠模拟耗时操作,在500ms内没有调用Disarm重置时间,导致Alarm方法被调用
  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(600));
  watchdog.Disarm(); //重置时间,再次计时
  watchdog.Arm();
  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(600));

  LOG(INFO) << watchdog.alarm_counter();  //  OUTPUT 2
}
  • ThreadLocalPointer,posix标准下的实现是基于pthread_key_*系列函数,也就是NLTP pthread的线程存储的实现,通过pthread_key_create为每一个线程分配一个内部的key到指针的映射,还可以自定义删除器。然后通过pthread_getspecific得到指针,第一次为NULL,需要使用者分配内存。然后利用pthread_setspecific指定。主要就是实现下面几个函数:
bool PlatformThreadLocalStorage::AllocTLS(TLSKey* key) {
  return !pthread_key_create(key,
      base::internal::PlatformThreadLocalStorage::OnThreadExit);
}
void PlatformThreadLocalStorage::FreeTLS(TLSKey key) {
  int ret = pthread_key_delete(key);
  DCHECK_EQ(ret, 0);
}
void* PlatformThreadLocalStorage::GetTLSValue(TLSKey key) {
  return pthread_getspecific(key);
}
void PlatformThreadLocalStorage::SetTLSValue(TLSKey key, void* value) {
  int ret = pthread_setspecific(key, value);
  DCHECK_EQ(ret, 0);
}

ThreadLocalPointer在此基础上做了更一步的封装,如下:


int main() {
  // Case 1
  base::ThreadLocalBoolean tlb;  //直接模板化了一个bool类型的线程本地存储
  if(!tlb.Get()) {
    LOG(INFO) << "tlb is nullptr";
  }

  tlb.Set(false);

  if(!tlb.Get()) {
    LOG(INFO) << "tlb is false";
  }

  tlb.Set(true);

  // Case 2
  char *pc = new char[10];
  memcpy(pc,"123456789\0",10);
  base::ThreadLocalPointer<char> p; //指定线程存储数据的类型
  if (p.Get() == nullptr) {
    p.Set(pc);
  }

  LOG(INFO) << p.Get();

}

gcc也提供了一套线程存储的实现,使用起来比NLTP pthread的实现更简单,__pthread,唯一的缺点就是线程存储的类型必须是POD类型,不过你可以使用指针指向一个非POD类型,这样就和NLTPpthread线程存储使用起来差不多了,关于gcc的线程存储的使用可以参考Thread-Local

  • guid UUID的实现,实现标准参考RFC4122,使用起来还是比较简单的。
int main() {
  std::string clientid = base::GenerateGUID();   //生成UUID字符串形式
  LOG(INFO) << clientid;

  if (base::IsValidGUID(clientid)) {             //判断是否是有效的UUID
    LOG(INFO) << "valid guid";
  }

  if (base::IsValidGUIDOutputString(clientid)) { //判断是否是有效的UUID,并且其中的字母是小写
    LOG(INFO) << "valid guid and a~f also lower";
  }

}
  • Environment类,其派生类EnvironmentImpl负责具体的实现,通过静态方法Create,创建了一个std::unique_ptr<Environment>指向具体的实现,posix标准下是通过getenvsetenv来查询和设置环境变量的。
int main() {
  std::unique_ptr<base::Environment> env(base::Environment::Create());
  std::string env_value;
  if (env->GetVar("PATH",&env_value)) {   // 查询环境变量
    LOG(INFO) << env_value;
  }

  const char kFooUpper[] = "FOO";
  const char kFooLower[] = "foo";

  env->SetVar(kFooUpper,kFooLower);       // 设置环境变量

  if (env->GetVar(kFooUpper,&env_value)) {
    LOG(INFO) << env_value;
  }

  env->UnSetVar(kFooUpper);               // unset环境变量
  if (env->HasVar(kFooUpper)) {           // 查询是否存在某个环境变量
    LOG(INFO) << env_value;
  } else {
    LOG(INFO) << "not env variable:" << kFooUpper;
  }

  const char* const a2[] = {"A=2",NULL};
  base::EnvironmentMap changes;           //一个map的typedef,用来保存环境变量
  std::unique_ptr<char* []> e;
  e = base::AlterEnvironment(a2,changes);
  LOG(INFO) << e[0];  // OUTPUT A=2

}
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值