1. 程式人生 > >Chromium base庫介紹

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

}