[c++] 為什麼C ++程序員應該盡量減少對'新'的使用?


Answers

因為堆疊速度快而且萬無一失

在C ++中,只需一條指令即可為堆棧上的空間分配給定函數中的每個本地作用域對象,並且不可能洩漏任何內存。 該評論打算(或應該打算)說“使用堆棧而不是堆”。

Question

我偶然發現堆棧溢出問題當使用std :: list <std :: string>時,使用std :: string發生內存洩漏 , 其中一個註釋說:

停止使用new東西。 我看不出任何你在任何地方使用新的理由。 您可以在C ++中按值創建對象,這是使用該語言的巨大優勢之一。 您不必分配堆中的所有內容。 不要像Java程序員那樣思考。

我不確定他的意思。 為什麼應該盡可能經常通過C ++的價值創建對象,它在內部會產生什麼差異? 我誤解了答案嗎?




我傾向於不同意使用新“太多”的想法。 雖然原始海報對系統類的新用法有點荒謬。 ( int *i; i = new int[9999]; really? int i[9999];更清晰。)我認為就是評論者的山羊。

當你使用系統對象時,你很少會需要多個對同一個對象的引用。 只要價值是一樣的,那就是重要的。 系統對象通常不佔用內存中的太多空間。 (每個字符一個字節,一個字符串)。 如果他們這樣做,這些庫應該被設計成考慮到內存管理(如果它們寫得很好)。 在這些情況下(除了代碼中的一兩個新聞外),新增功能實際上毫無意義,只會引入混淆和潛在錯誤。

然而,當你使用自己的類/對象時(例如原來的海報的Line類),那麼你必須開始考慮內存佔用,數據持久性等問題。 在這一點上,允許多次引用相同的值是無價的 - 它允許像鏈接列表,字典和圖形這樣的結構,其中多個變量不僅需要具有相同的值,還需要在內存中引用完全相同的對象 。 但是,Line類沒有任何這些要求。 所以原始海報的代碼實際上對於new絕對沒有需要。




new是新的goto

回想一下為什麼goto如此唾罵:雖然它是流量控制的強大的低級工具,但人們經常以不必要的複雜方式使用它,導致代碼難以遵循。 此外,最有用和最容易閱讀的模式被編碼在結構化編程語句中(例如forwhile ); 最終的結果是, goto代碼是相當少見的適當方式,如果你想寫goto ,那麼你可能做得很糟糕(除非你真的知道你在做什麼)。

new是相似的 - 它經常被用來使事情變得不必要的複雜和難以閱讀,並且最有用的使用模式可以被編碼已經被編碼到各種類中。 此外,如果您需要使用任何尚未有標準類的新使用模式,您可以編寫自己的類來對其進行編碼!

由於需要配對new語句和delete語句,我甚至會認為new gotogoto 更糟

就像goto,如果你認為你需要使用new,你可能會做的很糟糕 - 尤其是如果你在實現一個類的實現之外這樣做,它的目的是封裝你需要做的動態分配。




在很大程度上,這是一個將自己的弱點提升為一般規則的人。 使用new運算符創建對象本身沒有任何問題。 有什麼爭論是因為你必須遵守一些紀律:如果你創建了一個對象,你需要確保它將被銷毀。

這樣做的最簡單方法是在自動存儲中創建對象,因此C ++知道在超出範圍時將其銷毀:

 {
    File foo = File("foo.dat");

    // do things

 }

現在,請注意,當你在結束大括號之後脫離該塊時, foo超出了範圍。 C ++會自動為你調用它的dtor。 與Java不同,您無需等待GC找到它。

你寫了嗎

 {
     File * foo = new File("foo.dat");

你會想明確地與它匹配

     delete foo;
  }

或者甚至更好,將您的File *分配為“智能指針”。 如果你不小心會導致洩漏。

答案本身就是錯誤的假設,如果你不使用new你不會在堆上分配; 實際上,在C ++中,你不知道這一點。 至多,你知道一小部分內存,比如一個指針,肯定是分配在堆棧上的。 但是,請考慮File的實現是否類似

  class File {
    private:
      FileImpl * fd;
    public:
      File(String fn){ fd = new FileImpl(fn);}

那麼FileImpl仍將在堆棧上分配。

是的,你最好確定有

     ~File(){ delete fd ; }

在課堂上; 沒有它,即使你根本沒有明確分配堆,你也會從堆中洩漏內存。




兩個原因:

  1. 在這種情況下沒有必要。 你讓代碼不必要地變得更複雜。
  2. 它會在堆上分配空間,這意味著您必須記住稍後將其delete ,否則會導致內存洩漏。



當你使用new時,對像被分配給堆。 它通常用於預計擴展。 當你聲明一個對像如,

Class var;

它被放置在堆棧上。

你將永遠不得不打電話銷毀你放在堆上的新物件。 這打開了內存洩漏的可能性。 放置在堆棧上的對像不容易洩漏內存!




因為即使將結果包裝到智能指針中,它也容易發生細微的洩漏。

考慮一個“小心”的用戶,他記得在智能指針中包裝對象:

foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

此代碼很危險,因為不能保證 T1T2 之前構建shared_ptr 。 因此,如果其中一個new T1()new T2()在另一個成功後失敗,則第一個對象將被洩漏,因為不存在shared_ptr來銷毀和釋放它。

解決方案:使用make_shared




new創建的對象必須最終delete以免洩漏。 析構函數不會被調用,內存不會被釋放,整個位。 由於C ++沒有垃圾收集,這是一個問題。

由值(即堆棧)創建的對像在超出範圍時會自動死亡。 編譯器插入析構函數調用,並在函數返回時自動釋放內存。

auto_ptrshared_ptr這樣的智能指針解決了懸掛參考問題,但它們需要編碼規範並且有其他問題(可複制性,參考循環等)。

另外,在多線程場景中, new是線程之間的爭用點; 可能會對過度使用new產生性能影響。 由於每個線程都有自己的堆棧,因此創建堆棧對像是按照線程定義的。

值對象的缺點是,一旦宿主函數返回,它們就會死亡 - 只能通過複製或按值返回,不能將引用傳遞給調用者。




Related