今天,我們接續昨天介紹的守則,繼續來看看更具體 "Make interfaces easy to use correctly and hard to use incorrectly" 的實例跟介紹吧!
接續昨天最後我們有提到一致性很重要,這樣可以讓user了解之後就都依一樣的使用方式,反之,如果使用起來有很多前提或特殊的使用方式,就會讓user容易忘記這些複雜的規則,進而用錯。以以下這個factory function為例,它return的是一個動態分配的物件:
Investment* createInvestment();
而它return出來的東西需要記得去delete,才不會有memory leak。這就是一種 "額外的規則",使用上容易會有兩個潛在危機: 1. delete失敗 2. 重複delete同一個pointer。要避免這個問題,如同前面[Day 19] Use objects to manage resource有提到的,我們可以用smart pointer來管理這個資源,但user也可能會忘記放到smart pointer裡面。所以在這個例子中,好的設計的例子就是直接讓這個factor function return的就是一個smart pointer:
std::shared_ptr<Investment> createInvestment();
這樣就消滅了user錯用─忘記delete等等的情形。
同時,我們也可以把正確的delete行為都顧到─例如假使它的資源清理行為應該是getRidOfInvestment
,也可以把deleter寫進去,避免user用錯清理方式,例如:
std::shared_ptr<Investment> createInvestment()
{
std::shared_ptr<Investment> retVal(new Investment, getRidOfInvestment);
...
return retVal;
}
另外,這同時也可以避免 "cross-DLL 問題"。
所謂的 cross-DLL 問題會發生在其中一個DLL使用用new
來產生的物件,但在其他DLL裡面被delete
,細節可以參考其他參考資料,簡而言之,shared_ptr
可以避免此問題,因為它會在同一個DLL中去delete
,這樣這個createInvestment
產出的return就可以在不同DLL中被安全的使用。
今天講了這麼多shared_ptr,但重點其實不是在介紹shared_ptr的用法,而是說明我們考量到user可能會犯的錯,而採用shared_ptr是一種可以避免各種可能錯誤的設計。當然使用shared_ptr也是有代價的,例如它比raw pointer更耗空間,也比較慢,但相比之下它可以避免的錯誤價值更大。
貼心重點提醒:
- Good interfaces are easy to use correctly and hard to use incorrectly. You should strive for these characteristics in all your interfaces.
- Ways to facilitate correct use include consistency in interfaces and behavioral compatibility with built-in types.
- Ways to prevent errors include creating new types, restricting operations on types, constraining object values, and eliminating client resource management responsibilities.
shared_ptr
supports custom deleters. This prevents the cross-DLL problem, can be used to automatically unlock mutexes, etc.
這個守則的要點真多XD 再整理一下要點的要點─首先就是將這個大原則銘記在心,正確好用!2是一致性,最好跟built-in type性質都一樣,3是一些設計方式包含用new types並限制它的值域,並把資源管理都設計掉。最後則是可以藉助shared_ptr
來避免掉一些常見errors。