面向對象的RAII怎麼處理阻塞型的資源獲取過程?

RAII要求在構造函數里獲取資源,但是資源獲取可能是個耗時的交互過程,比如需要網路對端回復響應。那麼讓一個構造函數阻塞著,會是一種值得接受的行為嗎?個人是很不喜歡這樣的風格,每次必單獨定義init()。


我建議你不要機械地理解RAII。

RAII 字面意思是說在對象構造的時候獲取資源,其實更本質的含義是:讓對象擁有(own)資源,在析構的時候釋放資源,這樣資源的生命期管理就和對象生命期管理統一起來,只要做到不泄漏對象,就能保證不泄漏資源。

至於你是讓構造函數自己創建資源,還是把資源創建好了再交給構造函數保管,在我看來沒有優劣之分。你完全可以加一個 static factory method 來返回構造好的對象,這樣用戶就不用管你裡邊是怎麼實現的了。


如果允許使用異常的話,構造函數里獲取資源也沒有什麼特別不好的。如果喜歡C風格的錯誤處理,考慮到C++構造函數沒有額外返回值,可以要求構造函數只初始化屬性,需要嚴格通過init之類的方法分配資源,或者使用factory方法,這基本上就是個人喜好問題。

構造函數里獲取資源真沒什麼問題,比如可以寫auto content=File().read() 多帥啊


單獨init的優勢不是不讓線程阻塞在構造函數上,而是方便用return一個返回值返回錯誤。

構造函數是可以卡的,這點沒任何問題,除了個人審美因素以外。卡在構造函數上和卡在init上其實並沒有什麼區別。構造函數和init的一個明確的區別是構造函數不能return一個東西出去,特別是很明顯的不能返回一個C風格的-1或者RET_ERR之類的常量,要返回東西只能繞路走,於是如果想要直接用C風格打一槍看一眼return code,只能用一個trivial的構造函數,以及一個返回return code的init()。

PS1:一個例外情況是,你需要用容器或者數組一下構建大量重實例,但是卻可能只用到其中一小部分,所以你希望lazy init。但我想不到必須這麼做的場景。完全可以改成lazy construct。

PS2:構造函數也可以返回return code。Oh no我不是讓你扔出來。你在被構造的對象里加一個bool valid = false;成員,構造成功了再給它設置成true,再來一個bool is_valid() const noexcept{ return valid;}。但是遺憾的是這麼做確實有點繁瑣。不過如果想用這個方法的話,你可以用宏或者公共基類來讓事情變得不那麼繁瑣。

PS3:個人更喜歡把資源創建好交給構造函數保管,並且傳一個基類而不是具體類的指針/引用/智能指針進去。用依賴注入方式避免一個類和它依賴的組件的具體實現綁定是個好習慣,減輕組件之間的耦合,也方便切換。不過目前個人還沒有遇到一個用起來稱手的C++ DI容器,所以咱一般自己充當人肉DI container,把所有new組件的邏輯集中到一起。

PS4:最後才發現這其實是個老墳而已,我們都使了洛陽鏟了。大家還是散了吧,答了也沒人看。


把阻塞操作做成方法,返回值是構造好的對象

然後去解決讓阻塞方法不卡住線程這個問題


然後就從卡在構造函數變成卡在init了,有區別嗎?

你使用對象時是不是還得判斷對象是否初始化?又多了一堆潛在的bug。話說寫c-style程序的時候指針沒初始化的bug的坑踩過沒?哦對了,你這個比野指針還不容易看出來,因為你的對象是合法的。

說實話如果獲取資源和對象構造有必要分開的話,這個資源的獲取完全可以在別的函數完成,獲取完成後用一個不會阻塞的方法move進構造函數然後返回就好了。一個沒初始化完成的對象構造出來之後說實話也沒什麼意義,如果你有使用這個初始化一半的對象的需求,那麼你的這個類設計可能是有問題的。

單獨init(可能)唯一的作用就是——構造函數不能是虛函數,沒辦法多態,所以用兩段構造。但是這種時候用工廠方法更好。(其實最好是直接用反射)


個人覺得沒什麼不妥的,不知道題主獲取資源會不會出現time out或者類似的異常,那你在析構函數中release時候得考慮一下這個情況. 不過感覺你用非同步的方法也沒啥問題 不過你肯定要在未來的某個點等待資源準備就緒了. 個人感覺這些只是策略問題. 具體看你的應用情況而定。


考慮以下情況:

{

std::mutex mutex;

std::lock_guard lock(mutex);

}

這構造函數是可能阻塞的吧,沒毛病啊


C++ / Java 的 OO 模型處理存在阻塞、並發甚至簡單線程概念的稍微複雜一點的問題模型的時候,根本就是個半殘的玩具。

兩步初始化不過是讓整個東西實際上更殘了一些而已。


推薦閱讀:

在面向對象編程時對於類的劃分有哪些心得?
arraylist和array在內存分配和調用、編譯上有什麼本質區別?
c++為什麼要讓struct可以定義成員函數?
如何把思維從面向過程轉向面向對象?

TAG:面向對象編程 | C |