2015-02-07 176 views
2

我最近着迷於圖形編程,並且我注意到許多圖形引擎(即Ogre)和許多編碼器總體上更喜歡動態地初始化類實例。下面是從Ogre Basic Tutorial 1希望堆棧堆棧?

//... 

Ogre::Entity* ogreHead = mSceneMgr->createEntity("Head", "ogrehead.mesh"); 
Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("HeadNode"); 

//... 

ogreHeadheadNode數據成員和方法然後被稱爲ogreHead->blabla一個例子。

爲什麼要亂用對象指針而不是普通對象?

順便說一句,我也讀過的地方,堆內存分配比堆內存分配慢得多。

+0

請注意,許多庫通常都有一個預先分配的對象池(因此稱爲「對象池」),因此它們的「新」調用比提供的「新」更快。 – CoryKramer 2015-02-07 17:40:26

+1

我不明白。 「預分配對象池」是什麼意思? – Pilpel 2015-02-07 17:42:10

+4

一個對象池基本上是「我要去'新'50個'Foo'的對象,當有人叫'new'時,我會重新初始化其中的一個,讓它們使用它。 '刪除'我只是把對象拿回來重用。「這節省了構造函數和析構函數的開銷時間。 https://en.wikipedia.org/wiki/Object_pool_pattern – CoryKramer 2015-02-07 17:43:27

回答

5

堆分配不可避免地比堆棧分配慢得多。更多關於「慢多少?」後來。但是,在許多情況下,選擇是「爲您製造」,原因如下:

  1. 堆棧有限。如果你用完了,應用程序幾乎總是被終止 - 沒有真正的好恢復,甚至打印出錯信息說「我用完了堆棧」可能很難...
  2. 堆棧分配「消失」時你離開分配的功能。
  3. 變異性定義得更好,易於處理。 C++不能很好地處理「可變長度數組」,並且不能保證在所有編譯器中都能正常工作。

堆棧比堆棧慢多少?

我們會稍微討論一下「是否重要」。

對於給定的分配,堆棧分配是一個簡單的減法運算[1],在此,最起碼newmalloc將是一個函數調用,甚至可能是最簡單的分配將是幾十個指令,在複雜的情況下成千上萬[因爲內存必須從操作系統中獲得,並清除以前的內容]。因此,從10倍到「無限」慢,給予或帶走。確切的數字將取決於代碼運行的確切系統,分配的大小,並且通常是「以前對分配器的調用」(例如,一個很長的「釋放」分配列表可以使分配一個新對象變得更慢,因爲必須搜索一個合適的適配器)。當然,除非您使用堆管理的「鴕鳥」方法,否則您還需要釋放該對象並應付「內存不足」,從而爲代碼的執行和複雜性增加更多代碼/時間。但是,通過一些合理的巧妙編程,這可能大部分是隱藏的 - 例如,在對象的整個生命週期內分配長時間分配的東西將是「無需擔心」。在3D遊戲中爲每個像素或每個trianle分配堆中的對象將明顯是一個壞主意。但是如果對象的生命週期是多幀甚至整個遊戲,那麼分配和釋放它的時間將幾乎沒有。

同樣,不是做10000個單獨的對象分配,而是10000個對象。對象池就是這樣一個概念。

此外,分配時間通常不是花費時間的地方。例如,從磁盤文件中讀取三角形列表比爲相同三角形列表分配空間花費的時間要長得多 - 即使分配每個三角形列表也是如此!

對我來說,規則是:

  1. 是否很好地貼合在堆棧上?通常幾千字節是好的,許多千字節不太好,兆字節絕對不行。
  2. 數字(例如對象數組)是否已知,並且可以將它放在堆棧上的最大數量?
  3. 你知道對象是什麼嗎?換句話說抽象/多態類可能需要在堆上分配。
  4. 它的壽命與它所處的範圍相同嗎?如果沒有,請使用堆(或進一步向下堆棧,並將其傳遞到堆棧)。

[1]或者如果堆棧是「向高地址增長」添加 - 我不知道有這樣一個架構的機器,但它是可以想象的,我想一些已經被制定。 C當然不會承認堆棧增長的方式,或者關於運行時堆棧如何工作的任何其他內容。

+0

還有一個C++問題。我需要設計要在堆上分配的類嗎?我的意思是我應該以不同的方式編寫我的類,如果我知道我要在堆上創建它們的實例? – Pilpel 2015-02-07 19:39:16

+1

我通常不會。我希望我的類可以創建爲「堆棧式還是堆式」[一般來說,當然有些情況下,您「知道」它們將以某種特殊方式創建,但創建方法不會改變對象的設計方式 - 這是關於「我試圖解決什麼問題,以及我們如何解決這個問題」] – 2015-02-07 19:41:48

+0

我剛想到一個問題。爲什麼有一個池,而不是讓程序員確定他想要擁有多少對象,而不是在類中封裝一些預分配的實例?他是否足夠聰明地知道他可以在他的代碼開始時爲他想要或可能想要的所有對象分配數據? – Pilpel 2015-02-07 21:09:31

4

堆棧的範圍是有限的:它只存在於一個函數中。現在,現代的用戶接口程序通常是事件驅動的,這意味着你的函數被調用來處理一個事件,然後該函數必須返回以便程序繼續運行。因此,如果您的事件處理函數希望創建一個在函數返回後仍然存在的對象,顯然,該對象不能分配到該函數的堆棧中,因爲函數返回後該對象將立即停止存在。這是我們爲什麼在堆上分配東西的主要原因。

還有其他原因。

有時候,編譯期間不知道類的確切大小。如果類的確切大小未知,則不能在堆棧上創建,因爲編譯器需要準確瞭解需要爲堆棧中的每個項目分配多少空間。

此外,經常使用工廠方法,如whatever::createEntity()。如果你必須調用一個單獨的方法爲你創建一個對象,那麼這個對象不能在堆棧上創建,因爲這個答案的第一段中解釋了這個原因。

1

爲什麼用指針代替對象?

因爲指針可以讓事情變得更快。如果您按值傳遞的對象,另一個函數,例如

shoot(Orge::Entity ogre) 

,而不是

shoot(Orge::Entity* ogrePtr) 

如果怪物是不是指針,發生的事情是要傳遞整個對象到函數,而不是參考。如果編譯器沒有進行優化,則會留下一個效率不高的程序。還有其他一些原因,使用指針,你可以修改傳入的對象(有些人認爲引用更好,但這是一個不同的討論)。否則,你會花費太多時間來回複製修改的對象。

爲什麼堆?

  1. 從某種意義上說,堆是一種更安全的內存類型,可以讓您安全地重置/恢復。如果您撥打新電話並且沒有記憶,您可以將其標記爲錯誤。如果你正在使用堆棧,實際上沒有什麼好的方法可以知道你已經引起了堆棧溢出,沒有其他監督程序,此時你已經處於危險區域。
  2. 取決於您的申請。堆棧具有本地範圍,所以如果對象超出範圍,它將爲對象釋放內存。如果你需要其他功能的對象,那麼沒有真正的方法來做到這一點。
  3. 對OS應用更多,堆比堆棧大得多,尤其是在多線程應用程序中,每個線程可以具有有限的堆棧大小。
+0

我想你可能會在這個答案中迂迴一點。例如,如果你正在傳遞一個指針,那麼你定義爲「通過引用傳遞」,這在你的回答中似乎區分爲不同的東西。另外,關於堆與堆棧的安全性的評論值得懷疑。不經意間覆蓋堆上的內存指針也同樣容易。事實上,在某些方面,堆棧更安全。雖然我們在代碼中允許的溢出條件是非常嚴重的,但至少在堆棧展開時(假設我們沒有覆蓋框架),我們可以返回到良好的狀態。 – 2015-02-07 19:34:55

+0

關於堆棧的公平點可以更安全,但我的觀點並不是堆棧或堆被別的東西腐敗。堆更容易讓你知道是否有足夠的內存來分配(通過檢查新的返回值),而堆棧中的內存不一樣。 有關引用的注意事項是,在C++中引用不能爲空,而必須指向一個對象,但指針可以爲null。 – embeddedninja 2015-02-12 17:05:20