標籤:

在c++中,一個對象的內存分配問題?

比如說,在一個類中有一個指針,需要對這個指針分配內存,那麼,在構造函數中就分配好還是另外定義一個init函數,在init操作中分配,哪種比較合理?


我還是傾向於在構造函數里做好,順便可以學習一下如何正確處理異常,提高自己的C++水平。你說的第二種不就是MFC喜歡的兩階段構造嗎,用的時候超級噁心。


看需求和語義

比如說A類里有B對象,放在構造函數里的語義是說A在構造的時候必須有B,放在Init里的語義是說A在構造完成後可以暫時沒有B,在之後的需要的時候再設定B。

然後看所有權,如果B屬於A管理,那麼可以用B對象或指針,如果A里只是用B來引用外部對象,那麼如果是必要的,就用B引用,否則用B指針。

至於是否用share_ptr等就看其他需要了。

在B屬於A管理時,一般來說實踐編程時會有幾種情況:

  1. B是A的必要組成部分,比如說Rect里有point和size,那麼這個B(point)就直接用對象,不要用指針。

  2. B是A的必要組成部分,但B其實是用來管理一塊堆內存,比如string里的char* buffer指針,那一般也是在構造函數和相應的resize函數里構造B。
  3. B是A的必要組成部分,但B可以指向不同的對象,是一個多態基類指針。這個時候也推薦在構造函數里完成new具體對象。用A的構造函數參數提供B構造的信息。
  4. B是A的必要組成部分,但B太過笨重,想要延遲構造,那可以用一個LazyPtr&來把B包裝起來,在具體函數調用時再構造B,這時也不太推薦二段式構造。

  5. B是A的可選部分,但B很輕量,比如A里有一個string userdata.這個時候就直接用B對象吧。沒必要string* userdata給自己找麻煩,且效率也不高。
  6. B是A的可選部分,B是個比較大的對象或者是個多態對象,現代C++會推薦你用unique_ptr,然後在相應的init函數里構造吧。

一般來說A里如果只有一個B,那麼一般不會用init二段式構造,只會在一個特定的set函數里再去構造。而當A里要管理B,C,D等,且BCD還共同依賴於幾個參數一起構造,那麼這個時候才是寫個init一起構造BCD的時候。

然後還有一種慣用法是在單件模式時提供一個init函數,因為一般情況下單件模式都是調用的默認無參數構造函數,且需要顯式的安排各個單件的init順序,這個時候還是一般用init的。但這個裡面也是會細分,哪些東西可以直接放單件的默認構造里,哪些是在init時候才構造的。

再看錯誤處理,因為構造函數沒有返回值。init的錯誤處理一般是用bool或者錯誤碼返回值,是C式的。構造函數用拋出異常來處理錯誤。所以用init還是構造,也要看這裡面有可能發生的錯誤是否是可容許的。對於性能敏感的程序,異常也是有一定的性能負擔的,所以如果對於可容許預測的錯誤,比如init里打開一個文件,那就直接可以用返回值判斷文件是否正確打開等,那還是推薦用init的。

再看是否可以重複構造,畢竟A的構造函數只能調用一次,而init,uninit可以調用多次,如果B在A裡面會經歷生生死死,那還是用init吧。

可能還沒有說全各種情況,仔細思考A和B的關係,用適合的方式,但還是儘力推薦構造函數,讓語言機制幫你做得更多,讓你寫得更少。

寫的越多,bug越多。


根據你的項目類型而定。

如果是用c++寫c,就單獨拿出來。

別的應該都儘可能封裝起來。


感覺絕大多數都應該做構造函數裡面寫吧。

但是看到過一個圖形引擎的代碼,裡面的類各種相互引用,比如HAL裡面有TextureManager,ShaderManager,ContentManager的指針,而ShaderManager,ContentManager,TextureManager又有HAL的指針,而且ShaderManager還有ContentManager的指針等等(大概就是這樣複雜的依賴關係,但是具體沒有再去查證)。

這種情況下要在構造函數裡面構造不就循環下去了嘛~所以用的構造函數只是分配空間,init跑了以後才能正常工作。


構造函數里分配,析構函數里釋放。(如果沒其他需求的話)


簡單不易出錯的初始化,放在構造函數中。複雜易出錯的初始化,放在單獨的init中。可參考google style guide中的解釋:Google C++ Style Guide


推薦閱讀:

電腦的主存儲器和主存、內存的關係是什麼?
為什麼沒有編程語言的內存管理是手動管理與自動垃圾回收相結合的?
linux怎麼管理空閑內存?
如何釋放Python佔用的內存?
看遊戲引擎架構內存管理有個地方不太清楚?

TAG:內存管理 | CC |