還是不錯的一章。儘管看了也用過了挺多的Smart Poniter了,但是看到這章還是引發了很多的遐想。
首先第一點,並非所有的只能指標都需要重載->和*這兩個操作符,甚至要禁止這樣的操作符,道理很簡單。像作業系統中的這些Handle,這些東西本身就是訪問系統資源的一個標記。並沒有什麼實際的成員函數,所以對他們而言,這樣的* ->沒有什麼意義的。而如果要控制這些的存取層級,無疑反覆提到的policy模型又有用武之地了(感覺這個Loki反覆的強調了Policy的巨大意義)
第二點就是成員函數,這個topic是之前沒有認真想過的,現在想來確實非常有意義。問題的簡單描述就是這樣,可能有人覺得給SmartPtr添加一些介面是不錯的選擇,比如說Acquire和Release用來管理實際指向的pointee_,但是問題在於如果看到一下的情形相信很多人會像我一樣抓狂了:)
- SmartPtr<Printer> spRes = ...;
- spRes->Acquire(); //acuquire the printer
- ...// print a document...
- spRes->Release(); // release the printer
- spRes.Release();// release the pointer to the printer
spRes同時可以用.和->來調用名字相同的介面,無論如何都是讓人很疑惑容易出錯的東西,所以解決方案很簡單,我們設計的智能指標不存在成員函數:)如果有這樣效用的函數需求,可以把那些函數全域化,最多設成SmartPtr的友元。
第三點就是最重要,也是最主流的討論點,就是OwerShip的問題。像auto_ptr那樣的語義還是deep copy?(真沒想到過他,好像主流的就是應用計數(Ref Count))。下面就這樣的問題展開來看看到底這個OwnerShip有些什麼主流的解決方案。
A. 最沒有想到的是第一個Deep Copy,這個我們在C++中耳熟能詳的技術在這裡同樣有著意義。當然第一反應就是和C++中的傳統指標成員變數深拷貝的意義一致,就是每個SmartPtr都可以維護一個pointee_對象,那樣就不存在什麼擁有和刪除的問題,每個人管自己的。但是這樣最明顯的問題就是這個語義和傳統指標的意義已經不同了(這是我的觀點啦)因為他們已經指向了兩個內容相同的不同的對象(很拗口:))
比如說
- SmartPtr<Widget> spW1 = ....
- SmartPtr<Widget> spW2 = spW1;
- // this may change the content or status of instance spW2 points to.
- spW2->noneConstFunction()...
- // but now spW1 is still the same....
- //Compare with pointer
- Widget* pW1 = new Widget();
- Widget* pW2 = pW1;
- // this may change the content or status of instance spW2 points to.
- pW2->noneConstFunction()...
- // here pW1 changed as well...
恩,前面有點說廢話了,回到這裡的Deep Copy,那這個策略在這裡的意義到底是什麼呢?According to Andrei Alexandrescu,it's polymorphism,書中給出的理由是在拷貝的時候可以依賴C++那個虛函數Clone函數。這個Clone的函數保證了Slicing是不會發生的,所以原來的多態還是可以在新的只能指標對象中得以延續。但仔細想象就是雞蛋問題,如果你不用深拷貝,根本就沒有新的對象,根本不需要處理多態,但是現在反過來用多態作為深拷貝的一個理由,實在有點說不過去,這點是我最不置可否的。。。
B. Copy on Write也是一直很常見的技術,原來要表達的意思也是可以讓多個指標指向同一個對象,等到某一個函數調用要真的影響到公用對象內容的時候才進行拷貝,是這個值變化不影響到其他的指標。但是在C++中關鍵的問題是不容易找到這樣一個點來進行拷貝,是不是非Const的成員函數調用都需要copy呢?我們這裡套用一句,copy或者不copy這是一個問題:)總而言之,你沒有辦法找到一個合適的插入點去決定是否有拷貝的發生,更極端的例子就是我用指標直接操縱public的成員變數的時候,確實我們什麼都做不了,所以這個策略在這裡並不適合。
C. Reference Counting
就像書裡說到的一樣,這個應該是最流行的方案了,滿足了指標的語義,同時不需要額外的拷貝,唯一要做的就是多申請一個int值來存放。這個東西放在什麼地方呢,這裡有很多個解決方案
方案一
方案二:
方案三:(這個最為霸道:)侵入式的)
D. Reference Linking,
這個想法不知道是如何萌發的,但是其實還是一個不錯的想法,說出來可能比較麻煩,但是畫出一個圖來就很清晰了
這裡很明顯每個pointee_指向的同一個對象,和引用技術不同的是他的指向同一個元素的策略是由一個雙鏈表來支撐的。這裡比之引用技術不好的地方就在於這個記憶體的使用上,很顯然每個SmartPtr對象都需要一個prev和next指標的空間。更大的問題在於環形依賴,試想看這樣的情況執行個體A有一個SmartPtr<B>,同時B裡面有一個SmartPtr<A>。而SmartPtr當然是不能預見到偵測到這樣的循環相依性的,會發生什麼就不言而喻了。。。
E. Destructive Copy
這個就是std::auto_ptr的策略,就是每次拷貝的時候都需要把ownership搬來搬去,如果要代碼的話就是這樣:
- SmartPtr(SmartPtr& src)
- {
- pointee_ = src.pointee_;
- src.pointee_ = 0;
- }
- SmartPtr& operator=(SmartPtr& src)
- {
- if (this != &src)
- {
- delete pointee_;
- pointee_ = src.pointee_;
- src.pointee_ = 0;
- }
- }
從上面的時候就可以看出,這是一種多麼兇險的實現,如果顯示的拷貝你是知道的那沒有問題,如果是以值作為參數傳遞呢??是不是就傳遞了之後自身就失效了?!就是由於這種風險,auto_ptr並沒有迎來足夠多的掌聲:)好了,到大致就是這麼幾種方案協助了職能指標進行了對彙總指標的Ownership的維護.
最後這裡我們還想講的一個東西就是pointee_的地址的問題,或者是他本身的問題.
如果是地址的話,我們給出一個&()操作符來完成&pointee_(pointee_地址)的返回合適嗎?回答是不合適!!!因為不知道什麼時候作為參數傳遞的時候,有人刪除了這個指標都未可知,而那個可憐的SmartPtr對此一無所知,後面的操作可以想象!!!
更可怕的事情是即使沒有&操作符,一個*操作符足以出賣你得乾乾淨淨,由於有使用者定義轉換的存在,在編譯以下代碼的時候,編譯器是不會報任何錯誤的
- SmartPtr<Widget> sp;
- delete sp; // compiler won't complain anything!!!
所以看來看去,指標指標畢竟不是指標,他所有的一切都是協調的產物,像指標又不是指標.真是一個挺尷尬的利器啊.好了今天就說到這裡吧,^_^