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::atomic
是SC
(sequence consistent)順序一致性模型的,也就是內部做了Barrier(有效能損耗),但是大多數場景下我們只是想使用atomic特性,因此base針對不同的記憶體模型進行了封裝,有NoBarrier
,Barrier
,accquire
,release
等等。AutoReset
給全域性變數提供預設值和新值,等AutoReset
析構了,就會恢復預設值base64
和base64url
提供了對url場景和一般場景下的base64編碼,具體可以參考RFC-4648BigEndian
一個大端序的bytesbuf。GetBuildTime
通過在build的時候,生成一個帶有編譯時間的常量的標頭檔案,可以通過這個標頭檔案得到二進位制程式的編譯時間。Bind/CallBack
和std::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
內部已經包含了next
和prev
指標,沒有額外的堆記憶體分配,其用法如下:
//任何想作為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";
}
}
}
ScopedVector
和std::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
型別,這樣就和NLTP
的pthread
執行緒儲存使用起來差不多了,關於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標準下是通過getenv
和setenv
來查詢和設定環境變數的。
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
}