這陣子真是太忙了, 連續做了四個課設。 當然這并不能作為好久沒寫博客的借口, 沒寫博客的主要原因只有一個: 懶。 最近又開始回顧C++的語法與特性(據說C++就是一門需要反復回顧的語言),以及學習C++的編程規范。 敲了C++Primer 5th 上的一道典型的練習題,紀念一下這即將過去的2016.
題目描述: 定義你自己版本的 StrBlobPtr, 更新 StrBlob類, 加入恰當的 friend 聲明及begin 和 end 成員。
這道題目主要是練習 智能指針 share_ptr 和 weak_ptr。
我的環境: Win10 + VS2015
聲明 StrBlob 類和 類StrBlobPtr的文件: StrBlob.h
1 #pragma once 2 #ifndef PRACTICE_STRBLOB_H_ 3 #define PRACTICE_STRBLOB_H_ 4 #include <memory> 5 #include <vector> 6 #include <string> 7 #endif PRACTICE_STRBLOB_H_ 8 9 // 對于 StrBlob 中的友元聲明來說,這個前置聲明是必要的 10 class StrBlobPtr; 11 class StrBlob { 12 friend class StrBlobPtr; 13 public: 14 typedef std::vector<std::string>::size_type size_type; 15 StrBlob():data(std::make_shared<std::vector<std::string>>()) { } 16 StrBlob(std::initializer_list<std::string>il):data(std::make_shared<std::vector<std::string>>(il)){ } 17 size_type size() const { return data->size(); } 18 bool empty() const { return data->empty(); } 19 // 添加和刪除元素 20 void push_back(const std::string &t) { data->push_back(t); } 21 void pop_back(); 22 // 元素訪問 23 std::string& front(); 24 std::string& back(); 25 const std::string& front() const; 26 const std::string& back() const; 27 28 // 返回指向首元素和尾元素的 StrBlobPtr 29 StrBlobPtr begin(); 30 StrBlobPtr end(); 31 private: 32 std::shared_ptr<std::vector<std::string>> data; 33 void check(size_type i, const std::string &msg) const; 34 }; 35 36 37 // 對于訪問一個不存在元素的嘗試, StrBlobPtr拋出一個異常 38 class StrBlobPtr { 39 public: 40 StrBlobPtr(): curr(0) { } 41 StrBlobPtr(StrBlob &a, size_t sz = 0): 42 wptr(a.data), curr(sz) { } 43 std::string& deref() const; 44 StrBlobPtr& incr(); // 前綴遞增 45 private: 46 // 若檢查成功, check返回一個指向 vector 的 shared_ptr 47 std::shared_ptr<std::vector<std::string>> 48 check(std::size_t i, const std::string& msg) const; 49 // 保存一個 weak_ptr, 意味著底層 vector 可能會被銷毀 50 std::weak_ptr<std::vector<std::string>> wptr; 51 std::size_t curr; // 在數組中的當前位置 52 }; 53 54 55 56
實現 StrBlob 類和 類StrBlobPtr的文件: StrBlob.cpp
1 #include "stdafx.h" 2 #include "StrBlob.h" 3 4 //----------------------------------- 5 // 類 StrBlob 的實現 6 //----------------------------------- 7 void StrBlob::pop_back() 8 { 9 check(0, "pop_back on empty StrBlob"); 10 data->pop_back(); 11 } 12 13 std::string& StrBlob::front() 14 { 15 // 如果 vector 為空, check 會拋出一個異常 16 check(0, "front on empty StrBlob"); 17 return data->front(); 18 } 19 20 std::string& StrBlob::back() 21 { 22 // 如果 vector 為空, check 會拋出一個異常 23 check(0, "back on empty StrBlob"); 24 return data->back(); 25 } 26 27 const std::string& StrBlob::front() const 28 { 29 check(0, "front on empty StrBlob"); 30 return data->front(); 31 } 32 33 const std::string& StrBlob::back() const 34 { 35 check(0, "back on empty StrBlob"); 36 return data->back(); 37 } 38 39 void StrBlob::check(size_type i, const std::string& msg) const 40 { 41 if (i >= data->size()) 42 throw std::out_of_range(msg); 43 } 44 45 // 必須在 StrBlobPtr 定義之后進行定義 46 StrBlobPtr StrBlob::begin() 47 { 48 return StrBlobPtr(*this); 49 } 50 51 // 必須在 StrBlobPtr 定義之后進行定義 52 StrBlobPtr StrBlob::end() 53 { 54 auto ret = StrBlobPtr(*this, data->size()); 55 return ret; 56 } 57 58 59 //---------------------------------------- 60 // 類 StrBlobPtr 的實現 61 //---------------------------------------- 62 63 std::string & StrBlobPtr::deref() const 64 { 65 auto p = check(curr, "dereferemce past end"); 66 return (*p)[curr]; // (*p) 是對象所指向的 vector 67 } 68 69 StrBlobPtr & StrBlobPtr::incr() 70 { 71 // 如果 curr 已經指向容器的尾后位置, 就不能遞增它 72 check(curr, "increment past end of StrBlobPtr"); 73 ++curr; // 推進當前位置 74 return *this; 75 } 76 77 std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string & msg) const 78 { 79 auto ret = wptr.lock(); // 檢查 vector 是否還存在 80 if (!ret) 81 throw std::runtime_error("unbound StrBlobPtr"); 82 if (i >= ret->size()) 83 throw std::out_of_range(msg); 84 85 return ret; // 否則, 返回指向 vector 的 shared_ptr 86 }
用于測試的文件: practice_12.19.cpp
1 // practice_12.19.cpp : 定義控制臺應用程序的入口點。 2 // 3 4 #include "stdafx.h" 5 #include "StrBlob.h" 6 #include <iostream> 7 using namespace std; 8 9 int main() 10 { 11 StrBlob blob{ "So", "Fun", "So", "Good" }; 12 StrBlobPtr blobPtr(blob); 13 cout << blob.front() << endl; 14 blobPtr.incr(); 15 cout << blobPtr.deref() << endl; 16 blobPtr = blob.begin(); 17 cout << blobPtr.deref() << endl; 18 cout << blob.back() << endl; 19 return 0; 20 }
運行結果截圖:
這道題目是很經典的,但是很不幸, C++Primer 5th (中文版)上,卻把這道題目的代碼寫錯了一丟丟。
回顧一下這道題涉及的主要姿勢:
一般程序使用動態內存的原因
- 程序不知道自己需要使用多少對象
- 程序不知道所需對象的準確類型
- 程序需要在多個對象間共享數據(這個例子就是共享數據的問題)
友元的相關知識
- 友元類一定要事先聲明(或定義)
- 需要用到友元類中的具體成員時,必須保證友元類已經定義。
列表初始化
頭文件定義規范
google 的 C++ 編碼規范之頭文件:
頭文件
通常,每一個.cc 文件(C++的源文件)都有一個對應的.h 文件(頭文件) ,也有一些例外,如單元測試代
碼和只包含main()的.cc 文件。
正確使用頭文件可令代碼在可讀性、文件大小和性能上大為改觀。
下面的規則將引導你避免使用頭文件時的各種麻煩。
1. #define 保護
所有頭文件都應該使用#define 防止頭文件被多重包含(multiple inclusion) ,命名格式為:
<PROJECT>_<PATH>_<FILE>_H_
為保證唯一性,頭文件的命名應基于其所在項目源代碼樹的全路徑。例如,項目 foo 中的頭文件
foo/src/bar/baz.h 按如下方式保護:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
2. 頭文件依賴
使用前置聲明(forward declarations)盡量減少.h 文件中#include 的數量。
當一個頭文件被包含的同時也引入了一項新的依賴(dependency) ,叧要該頭文件被修改,代碼就要重新
編譯。如果你的頭文件包噸了其他頭文件,這些頭文件的任何改變也將導致那些包含了你的頭文件的代碼
重新編譯。因此,我們應該盡量少的包含頭文件,尤其是那些包含在其他頭文件中的。
使用前置聲明可以顯著減少需要包含的頭文件數量。舉例說明:頭文件中用到類 File,但不需要訪問 File
的聲明,則頭文件中只需前置聲明 class File;無需#include "file/base/file.h"。
在頭文件如何做到使用類 Foo 而無需訪問類的定義?
1) 將數據成員類型聲明為 Foo *或 Foo &;
2) 參數、返回值類型為 Foo 的函數只是聲明(但不定義實現) ;
3) 靜態數據成員的類型可以被聲明為 Foo,因為靜態數據成員的定義在類定義之外。
另一方面,如果你的類是 Foo 的子類,或者含有類型為 Foo 的非靜態數據成員,則必須為之包含頭文件。
有時,使用指針成員(pointer members,如果是 scoped_ptr 更好)替代對象成員(object members)
的確更有意義。然而,返樣的做法會降低代碼可讀性及執行效率。如果僅僅為了少包含頭文件,還時不要
這樣替代的好。
當然,.cc 文件無論如何都需要所使用類的定義部分,自然也就會包含若干頭文件。
注:能依賴聲明的就不要依賴定義。
文章列表