英文版:http://google-styleguide.googlecode.com/svn/trunk/cppguide.xmlhtml
中文版:http://zh-google-styleguide.readthedocs.org/en/latest/google-cpp-styleguide/contents/node
(1) 習慣c++,const特性linux
(2) 構造、析構、賦值、copy and swapios
(3) RAII資源管理c++
(4) 傳值or傳引用、禁止返回局部對象指針、實現swapgit
(5) 少轉型、異常安全、inline、編譯依賴(pimpl手法)github
(6) 繼承本質、接口繼承、實現繼承算法
(1) 容器:分類、區間操做優於單元素循環操做、容器不是線程安全shell
(2) vector優於數組、string優於char*、vector的reverse函數、swap空容器技巧編程
(3) 關聯容器:不要使用[]操做,c++11標準hash容器std::unordered_map
(4) 迭代器: 提供越界檢查、連續型容器使用 distance在 迭代器切換 idx下標
(5) 算法:多用標準庫算法、各類排序相關算法、各類二分查找相關算法
(6) 函數對象:推薦陳碩大大: std::function std::bind替代虛函數
(7) 多使用STL,容器函數優於算法庫函數,list的sort函數
class Mystring { public: Mystring() : data_(new char[1]) { *data = '\0'; } Mystring(const char* str) : data_(new char[strlen(str) + 1]) { strcpy(data_, str); } Mystring(const Mystring& str) : data_(new char[str.size() + 1]) { strcpy(data_, str.c_str()); } ~Mystring() { delete[] data_; } // 重載賦值,採用copy and swap手法,舊式寫法 Mystring& operator=(const Mystring& str) { Mystring tmp(str); swap(tmp); return *this; } // 重載賦值,採用copy and swap手法,新式寫法 Mystring& operator=(Mystring& str) { swap(str); return *this; } int size() const { return (int)strlen(data_); } const char* c_str() const { return data_; } void swap(Mystring& str) { std::swap(data_, str.data_); } private: char* data_; };
智能指針類與普通指針同樣,但它藉由自動化內存管理保證了安全性,避免了諸如懸掛指針、內存泄露和分配失敗等問題。
智能指針有好幾種實現方式,STL和Boost庫裏都有實現,好比使用句柄類和引用計數方式。
咱們如今使用引用計數定義智能指針,智能指針類將一個計數器與類指向的對象相關聯。使用計數跟蹤該類有多少個對象共享同一指針。
使用計數爲0時,刪除對象。使用計數有時也稱爲引用計數(reference count)。
使用一個計數變量,並將其置一,每新增一個對象的引用,該變量會加一,移除一個引用則減一,
即當對象做爲另外一對象的副本而建立時,複製構造函數複製指針並增長與之相應的使用計數的值。
當對一個對象進行賦值時(=操做符),覆寫=操做符,這樣才能將一箇舊的智能指針覆值給另外一指針,舊的引用計數減一,新的智能指針的引用計數則加一。
#include <memory> #include <stdlib.h> template<typename T> class SmartPointer { public: SmartPointer<T>(T* ptr) { ref = ptr; ref_count = (unsigned*)malloc(sizeof(unsigned)); *ref_count = 1; } SmartPointer<T>(SmartPointer<T>& sptr) { ref = sptr.ref; ref_count = sptr.ref_count; ++(*ref_count); } SmartPointer<T>& operator=(SmartPointer<T>& sptr) { if (this == &sptr) { return *this; } --(*ref_count); if (*ref_count == 0) { clear(); } ref = sptr.ref; ref_count = sptr.ref_count; ++(*ref_count); return *this; } ~SmartPointer<T>() { --(*ref_count); if (*ref_count == 0) { clear(); } } T* GetValue() { return ref; } private: void clear() { delete ref; free(ref_count); ref = NULL; ref_count = NULL; } private: T* ref; unsigned* ref_count; }; int main() { int* ip1 = new int(); *ip1 = 1111; int* ip2 = new int(); *ip2 = 2222; SmartPointer<int> sp1(ip1); SmartPointer<int> sp2(ip2); SmartPointer<int> spa = sp1; sp2 = spa; return 0; }
POD(Plain Old Data):標量類型 或 傳統的C struct 類型,POD類型必然 擁有 默認的ctor、dtor、copy、assign
POD類類型就是指 class、struct、union,且不具備用戶定義的構造函數、析構函數、拷貝算子、賦值算子;不具備繼承關係,所以沒有基類;不具備虛函數,因此就沒有虛表;非靜態數據成員沒有私有或保護屬性的、沒有引用類型的、沒有非POD類類型的(即嵌套類都必須是POD)、沒有指針到成員類型的(由於這個類型內含了this指針)。
咱們能夠對 POD 型別採起最有效率的複製手法,而對 non-POD 型別採起最保險安全的做法
首先 利用迭代器萃取手法 萃取出迭代器 的 value type,而後判斷該型別是否爲 POD 型別
typedef typename __type_traits<T>::is_POD_type is_POD;
迭代器所指對象的型別,稱爲該迭代器的 value type
「模板參數推導機制」 只能針對於 函數參數類型,不能推導 函數的返回值類型
不是全部的迭代器都是 class type,原聲指針就不是!!!
STL(泛型思惟)絕對必須接受原生指針做爲一種迭代器,這個時候就須要針對特殊狀況(原生指針)作特化處理,即模板偏特化
模板偏特化:在泛化設計中提供一個特化版本(將泛化版本中的某些template參數賦予特殊的指定)
迭代器traits手法本質上就是 模板偏特化,實質代碼以下:
template <class T> struct iterator_traits { typedef typename T::value_type value_type; }
如今不論是class type 仍是 原生指針int* , 咱們均可以經過traits萃取出 迭代器的 value_type
注:能夠直接使用C++11的final關鍵字
多態兩必要條件:(1) virtual函數 (2) 基類指針(引用)指向派生類對象
多態知識點:
(1) 任何含有 virtual函數的類及其派生類 均在類實例對象的首地址 安插一個指向虛函數表的指針(虛表指針,vptr)
爲何在 類對象的首地址(即this指針處) 存放 虛表指針呢?
Base* pBase = new Derived; // Base對象首地址、Derived對象首地址、this指針、虛表指針 均相同,很是方便的定位 虛表指針
(2) Derived類 會繼承 Base類 的虛表,當Derived類 重定義了 Base類的某個函數,這時 Derived對應的虛函數表
Base::foo() ==> Derived::foo() // Derived::foo() 覆蓋從基類 繼承而來的 虛函數 Base::foo()
// c++中 全部non-static成員變量 和 virtual成員函數 都必須經過 this指針訪問 class test { public: test(int value) : val(value) { } void foo1() { // 正確:non-virtual函數地址編譯期肯定,傳入this指針,this指針爲空,可是這個函數沒有 訪問 this空指針 fprintf(stdout, "hello world\n"); } void foo2() { // 錯誤:non-virtual函數地址編譯期肯定,傳入this指針,this指針爲空,可是這個函數 訪問了 this空指針(由於 this->val) fprintf(stdout, "%d\n", val); } virtual void foo3() { // 錯誤:全部 virtual函數都須要經過 虛函數表肯定,虛函數表須要 this指針來肯定,所以 訪問了 this空指針 fprintf(stdout, "hello world\n"); } static void foo4() { // 正確:全部static函數都沒有this指針,因此不能訪問 non-static成員變量 fprintf(stdout, "hello world\n"); } private: int val; }; test* pTest = NULL; // this指針爲空 pTest->foo1(); pTest->foo2(); pTest->foo3(); pTest->foo4();
class Base {}; class Derived {}; Base* pBase = new Derived; Base* pBase = new Derived[10];
/* 1. 信息 = 比特位 + 解釋方式,類型轉換隻是改變了 解釋方式,數據未變 2. 數組和結構體(struct、class)訪問成員變量和成員函數 都是 首地址 + offset偏移 type[i] ==> type(首地址) + i * sizeof(type) 3. new Derived返回指向Derived類型的指針 4. pBase = pDerived 指針的類型轉換 5. 通常狀況下,sizeof(Base) 不等於 sizeof(Derived) 6. 爲何第一句正確呢? 由於 這一句 只分配了一個對象,而且 vptr位於對象首地址,所以 Base的首地址 == Derived的首地址 == this指針,故正確找到 vptr 7. 爲何第二句通常狀況下錯誤呢? 一般狀況下,sizeof(Base) 不等於 sizeof(Derived), 所以 pBase[i] 不等於 pDerived[i],因爲類型轉換改變了解釋方式,致使找不到Derived的正確this指針位置, 所以vptr、析構函數就徹底不正確了 注:當 sizeof(Base) == sizeof(Derived) 時,能夠正確運行,但這是一個未定義行爲,因此禁止這樣的寫法 */
#ifndef _JJALLOC_ #define _JJALLOC_ #include <new> // for placement new #include <cstddef> // for ptrdiff_t, size_t #include <cstdlib> // for exit() #include <climits> // for UINT_MAX #include <iostream> // for cerr namespace JJ { template<class T> inline T* _allocate(ptrdiff_t size, T*) { std::set_new_handler(0); T* tmp = (T*)(::operator new((size_t)(size * sizeof(T)))); if (tmp == 0) { std::cerr << "out of memory" << std::endl; exit(1); } return tmp; } template<class T> inline void _deallocate(T* buffer) { ::operator delete(buffer); } template<class T1, class T2> inline void _construct(T1* p, const T2& value) { new(p) T1(value); } template<class T> inline void _destroy(T* ptr) { ptr->~T(); } // std::allocator的標準接口 template<class T> class allocator { public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; template<class U> struct rebind { typedef allocator<U> other; }; pointer allocate(size_type n, const void* hint = 0) { return _allocate((difference_type)n, (pointer)0); } void deallocate(pointer p, size_type n) { _deallocate(p); } void construct(pointer p, const T& value) { _construct(p, value); } void destroy(pointer p) { _destroy(p); } pointer address(reference x) { return (pointer)&x; } const_pointer const_address(const_reference x) { return (const_pointer)&x; } size_type max_size() const { return size_type(UINT_MAX/sizeof(T)); } }; } #endif // _JJALLOC_
使用配置器:
std::vector<int, JJ::allocator<int>> vec
通常而言,咱們習慣的c++內存配置操做和釋放操做:
class Foo {...}; Foo* pf = new Foo; // 配置內存,而後構造對象 delete pf; // 析構對象,而後釋放內存
new關鍵字包含兩階段操做:
(1) 調用 ::operator new 配置內存
(2) 調用 Foo::Foo()構造對象內容
delete關鍵字包含兩階段操做:
(1) 調用 Foo::~Foo() 將對象析構
(2) 調用 ::operator delete 釋放內存
template<class T, class Alloc = alloc> class vector { public: typedef T value_type; typedef value_type* iterator; protected: iterator start; // 表示目前使用空間的頭 iterator finish; // 表示目前使用空間的尾 iterator end_of_storage; // 表示目前可用空間的尾 };
std::vector<int> vec; sizeof(vec) = 12; // 32位
https://cloud.github.com/downloads/chenshuo/documents/CppPractice.pdf
MutexLock 封裝臨界區(Critical secion),這是一個簡單的資源類,用 RAII 手法 [CCS:13]封裝互斥器的建立與銷燬。
臨界區在 Windows 上是 CRITICAL_SECTION,是可重入的;在 Linux 下是 pthread_mutex_t,默認是不可重入的。MutexLock 通常是別的 class 的數據成員。
MutexLockGuard 封裝臨界區的進入和退出,即加鎖和解鎖。MutexLockGuard 通常是個棧上對象,它的做用域恰好等於臨界區域。
#include <pthread.h> #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&) \ TypeName& operator=(const TypeName&) class MutexLock { public: MutexLock() { pthread_mutex_init(&mutex_, NULL); } ~MutexLock() { pthread_mutex_destroy(&mutex_); } void lock() { pthread_mutex_lock(&mutex_); } void unlock() { pthread_mutex_unlock(&mutex_); } pthread_mutex_t* getPthreadMutex() { return &mutex_; } private: pthread_mutex_t mutex_; DISALLOW_COPY_AND_ASSIGN(MutexLock); }; class MutexLockGuard { public: explicit MutexLockGuard(MutexLock& mutex) : mutex_(mutex) { mutex_.lock(); } ~MutexLockGuard() { mutex_.unlock(); } private: MutexLock& mutex_; DISALLOW_COPY_AND_ASSIGN(MutexLockGuard); }; #define MutexLockGuard(x) static_assert(false, "missing mutex guard var name")
rb_tree 的迭代器的每次遞增或遞減不能保證是常數時間,最壞狀況下多是對數時間(即與樹的深度成正比)
rb_tree_node* rb_tree_increment(rb_tree_node* node) { if (node == NULL) { return NULL; } if (node->right != NULL) { node = node->right; while (node->left != NULL) { // 右子樹最左下節點 node = node->left; } } else { rb_tree_node* parent = node->parent; while (node == parent->right) { // 一直上溯 node = parent; parent = node->parent; } if (node->right != parent) { node = parent; } } return node; }
那麼用 begin()/end() 迭代遍歷一棵樹仍是不是 O(N)?
換言之,迭代器的遞增或遞減是不是分攤後的(amortized)常數時間?
利用數學概括法能夠獲知:
對於深度爲 n 的滿二叉樹,有 2^n - 1 個元素,從 begin() 到 end() 須要走 f(n) 步。那麼 f(n) = 2*f(n-1) + n。
而後,用遞推關係求出 f(n) = sum(i * 2 ^ (n-i)) = 2^(n+1) - n - 2(這個等式能夠用概括法證實)。
即對於深度爲 n 的滿二叉樹,從頭至尾遍歷的步數小於 2^(n+1) - 2,而元素個數是 2^n - 1,兩者一除,獲得平均每一個元素須要 2 步。
所以能夠說 rb_tree 的迭代器的遞增遞減是分攤後的常數時間。
彷佛還有更簡單的證實方法,在從頭至尾遍歷的過程當中,每條邊(edge)最多來回各走一遍,一棵樹有 N 個節點,那麼有 N-1 條邊,最多走 2*(N-1)+1 步,也就是說平均每一個節點須要 2 步