C ++ programcıları neden 'yeni' kullanımını en aza indirmeli?


873

Ben tökezledi yığın taşması soru std :: liste <std :: string> kullanılırken std :: string ile Bellek sızıntısı ve yorumların biri bu diyor ki:

newÇok kullanmayı bırak . Yaptığınız hiçbir yerde yeni kullandığınız için hiçbir neden göremiyorum. C ++ 'da değere göre nesneler oluşturabilirsiniz ve bu dili kullanmanın en büyük avantajlarından biridir.
Öbek üzerindeki her şeyi ayırmanıza gerek yoktur. Java programcısı
gibi düşünmeyi bırakın .

Bununla ne demek istediğinden gerçekten emin değilim.

Nesneler neden olabildiğince sık C ++ değerinde oluşturulmalıdır ve dahili olarak ne fark eder?
Cevabı yanlış mı yorumladım?

Yanıtlar:


1037

Yaygın olarak kullanılan iki bellek ayırma tekniği vardır: otomatik ayırma ve dinamik ayırma. Genellikle, her biri için karşılık gelen bir bellek bölgesi vardır: yığın ve yığın.

yığın

Yığın, belleği her zaman ardışık bir şekilde tahsis eder. Bunu yapabilir, çünkü belleği ters sırayla serbest bırakmanızı gerektirir (First-In, Last-Out: FILO). Bu, birçok programlama dilinde yerel değişkenler için bellek ayırma tekniğidir. Çok, çok hızlı çünkü minimum defter tutma gerektiriyor ve tahsis edilecek bir sonraki adres örtük.

Depolama alanı kapsamın sonunda otomatik olarak talep edildiği için C ++ 'da buna otomatik depolama adı verilir . Geçerli kod bloğunun yürütülmesi (kullanılarak sınırlandırılmış {}) tamamlanır tamamlanmaz, o bloktaki tüm değişkenler için bellek otomatik olarak toplanır. Bu aynı zamanda yıkıcıların kaynakları temizlemek için çağrıldığı andır .

Yığın

Yığın daha esnek bir bellek ayırma moduna izin verir. Defter tutma daha karmaşıktır ve tahsis daha yavaştır. Örtük bırakma noktası olmadığından deleteveya delete[]( freeC) tuşunu kullanarak belleği manuel olarak serbest bırakmanız gerekir . Bununla birlikte, örtük bir serbest bırakma noktasının olmaması, yığının esnekliğinin anahtarıdır.

Dinamik ayırmayı kullanma nedenleri

Yığın kullanımı daha yavaş olsa ve potansiyel olarak bellek sızıntılarına veya bellek parçalanmasına yol açsa bile, daha az sınırlı olduğu için dinamik ayırma için mükemmel iyi kullanım durumları vardır.

Dinamik ayırmayı kullanmanın iki temel nedeni:

  • Derleme zamanında ne kadar belleğe ihtiyacınız olduğunu bilmiyorsunuz. Örneğin, bir metin dosyasını bir dizeye okurken, genellikle dosyanın ne büyüklüğüne sahip olduğunu bilmezsiniz, bu nedenle programı çalıştırana kadar ne kadar bellek ayıracağına karar veremezsiniz.

  • Geçerli bloktan ayrıldıktan sonra da devam edecek bir bellek ayırmak istiyorsunuz. Örneğin string readfile(string path), bir dosyanın içeriğini döndüren bir işlev yazmak isteyebilirsiniz . Bu durumda, yığın tüm dosya içeriğini tutabilse bile, bir işlevden geri dönemez ve ayrılan bellek bloğunu koruyamazsınız.

Dinamik ayırma neden genellikle gereksizdir?

C ++ 'da yıkıcı adı verilen temiz bir yapı vardır . Bu mekanizma, kaynak ömrünü bir değişkenin ömrü ile hizalayarak kaynakları yönetmenizi sağlar. Bu teknik RAII olarak adlandırılır ve C ++ 'ın ayırt edici noktasıdır. Kaynakları nesnelere "sarar". std::stringmükemmel bir örnek. Bu pasaj:

int main ( int argc, char* argv[] )
{
    std::string program(argv[0]);
}

aslında değişken miktarda bellek ayırır. std::stringBunu yıkıcı yığın bırakması kullanarak nesne bellek ayırır. Bu durumda, did not elle dinamik bellek tahsisi faydalarını var hala tüm kaynakları yönetmek ve gerek.

Özellikle, bu snippet'te:

int main ( int argc, char* argv[] )
{
    std::string * program = new std::string(argv[0]);  // Bad!
    delete program;
}

gereksiz dinamik bellek tahsisi var. Program daha fazla yazmayı (!) Gerektirir ve hafızayı yeniden yerleştirmeyi unutma riskini getirir. Bunu belirgin bir faydası olmadan yapar.

Otomatik depolama alanını neden mümkün olduğunca sık kullanmalısınız?

Temel olarak, son paragraf özetlemektedir. Otomatik depolamayı olabildiğince sık kullanmak programlarınızı yapar:

  • yazmak için daha hızlı;
  • çalıştırıldığında daha hızlı;
  • bellek / kaynak sızıntılarına daha az eğilimli.

Bonus puanlar

Referans verilen soruda ek endişeler var. Özellikle, aşağıdaki sınıf:

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("foo_bar");
}

Line::~Line() {
    delete mString;
}

Aslında aşağıdakilerden çok daha riskli:

class Line {
public:
    Line();
    std::string mString;
};

Line::Line() {
    mString = "foo_bar";
    // note: there is a cleaner way to write this.
}

Bunun nedeni, std::stringbir kopya oluşturucuyu doğru şekilde tanımlamasıdır. Aşağıdaki programı düşünün:

int main ()
{
    Line l1;
    Line l2 = l1;
}

Orijinal sürümü kullanarak, bu program muhtemelen deleteaynı dizede iki kez kullandığı için çökecektir. Değiştirilen sürüm kullanıldığında, her Lineörnek kendi dize örneğine sahip olacak , her biri kendi belleğine sahip olacak ve her ikisi de programın sonunda serbest bırakılacaktır.

Diğer notlar

Yaygın kullanımı RAII çünkü her şeyden nedenlerden C ++ en iyi yöntem olarak kabul edilir. Bununla birlikte, hemen belirgin olmayan ek bir fayda vardır. Temel olarak, parçalarının toplamından daha iyidir. Bütün mekanizma bestelemektedir . Ölçekler.

LineSınıfı bir yapı taşı olarak kullanırsanız :

 class Table
 {
      Line borders[4];
 };

Sonra

 int main ()
 {
     Table table;
 }

dört std::stringörnek, dört Lineörnek, bir Tableörnek ve dizenin tüm içeriğini ayırır ve her şey otomatik olarak serbest bırakılır .


55
Sonunda RAII'den bahsettiği için +1, ancak istisnalar ve yığın gevşemesi hakkında bir şeyler olmalı.
Tobu

7
@Tobu: evet, ama bu yazı zaten oldukça uzun ve OP'nin sorusuna odaklanmak istedim. Sonunda bir blog yazısı veya bir şey yazacağım ve buradan bağlantı vereceğim.
André Caron

15
Yığın tahsisi için dezavantajdan bahsetmek için harika bir ek olacaktır (en azından C ++ 1x'e kadar) - dikkatli değilseniz, gereksiz yere işleri kopyalamanız gerekir. mesela bir Monsterbir dışarı şişler Treasureiçin Worldölür. Onun içinde Die()yöntemle dünyaya hazine ekler. world->Add(new Treasure(/*...*/))Öldükten sonra hazineyi korumak için diğerlerinde kullanılmalıdır . Alternatifler shared_ptr(aşırı olabilir), auto_ptr(mülkiyetin devri için zayıf anlambilimsel), değere göre (israf) ve move+ unique_ptr(henüz yaygın olarak uygulanmamıştır) olabilir.
kizzx2

7
Yığına ayrılan yerel değişkenler hakkında söyledikleriniz biraz yanıltıcı olabilir. "Yığın", yığın karelerini depolayan çağrı yığını anlamına gelir . LIFO tarzında depolanan bu yığın çerçeveler. Belirli bir çerçeve için yerel değişkenler, bir yapının üyesi gibi tahsis edilir.
someguy

7
@someguy: Gerçekten, açıklama mükemmel değil. Uygulama, tahsis politikasında özgürlüğe sahiptir. Bununla birlikte, değişkenlerin bir LIFO tarzında başlatılması ve yok edilmesi gerekir, bu nedenle analoji geçerlidir. Cevabı daha da karmaşıklaştıran bir iş olduğunu sanmıyorum.
André Caron

171

Yığın daha hızlı ve sızdırmaz olduğundan

C ++ 'da, belirli bir işlevdeki her yerel kapsam nesnesi için - yığın üzerinde - alan ayırmak tek bir komut alır ve bu bellekten herhangi birini sızdırmak imkansızdır. Bu yorum, "yığını değil, yığını kullan" gibi bir şey söylemeyi amaçlamıştır (ya da hedeflemelidir) .


18
"alan ayırmak için tek bir talimat alır" - ah, saçmalık. Yığın işaretçisine eklemek yalnızca bir yönerge gerektirdiğinden emin olun, ancak sınıfın ilginç bir iç yapısı varsa, yığın işaretçisine eklenmekten çok daha fazlası olacaktır. Java'da alan ayırmak için herhangi bir talimat gerekmediğini söylemek de aynı derecede geçerlidir, çünkü derleyici referansları derleme zamanında yönetecektir.
Charlie Martin

31
@Charlie doğru. Otomatik değişkenler hızlıdır ve kusursuzdur .
Oliver Charlesworth

28
@Charlie: Sınıf içi her iki şekilde de ayarlanmalıdır. Karşılaştırma, gerekli alanın tahsisi ile yapılmaktadır.
Oliver Charlesworth

51
öksürük int x; return &x;
peterchen

16
hızlı evet. Ama kesinlikle kusursuz değil. Hiçbir şey kusursuz değildir. Bir StackOverflow alabilirsiniz :)
rxantos

107

Nedeni karmaşık.

İlk olarak, C ++ çöp toplanmaz. Bu nedenle, her yeni için ilgili bir silme işlemi olmalıdır. Bu silme işlemini gerçekleştiremezseniz, bellek sızıntısı olur. Şimdi, böyle basit bir dava için:

std::string *someString = new std::string(...);
//Do stuff
delete someString;

Bu basit. Ama "İşler yap" bir istisna atarsa ​​ne olur? Hata: bellek sızıntısı. "Bir şeyler yap" sorunu returnerken çıkarsa ne olur ? Hata: bellek sızıntısı.

Ve bu en basit durum için . Eğer bu dizeyi birine geri verirseniz, şimdi silmeniz gerekir. Ve bir argüman olarak geçerse, onu alan kişinin silmesi gerekir mi? Ne zaman silmeliler?

Veya bunu yapabilirsiniz:

std::string someString(...);
//Do stuff

Hayır delete. Nesne "yığın" üzerinde oluşturuldu ve kapsam dışına çıktığında imha edilecek. Hatta nesneyi döndürebilir, böylece içeriğini çağrı işlevine aktarabilirsiniz. Nesneyi işlevlere iletebilirsiniz (genellikle bir başvuru veya const başvurusu olarak: void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)vb.).

Hepsi newve olmadan delete. Belleğin kimin sahibi olduğu veya silmekten kimin sorumlu olduğu hakkında hiçbir soru yok. Yaparsan:

std::string someString(...);
std::string otherString;
otherString = someString;

Anlaşılmaktadır otherStringbir kopyası vardır verilerin arasında someString. İşaretçi değil; ayrı bir nesnedir. Aynı içeriğe sahip olabilirler, ancak birini diğerini etkilemeden değiştirebilirsiniz:

someString += "More text.";
if(otherString == someString) { /*Will never get here */ }

Fikri görüyor musunuz?


1
Bu notta ... Bir nesne dinamik olarak ayrılırsa main(), program süresince var olur, durum nedeniyle yığın üzerinde kolayca oluşturulamaz ve ona işaretçiler, erişim gerektiren herhangi bir işleve geçirilir. , bir program çökmesi durumunda sızıntıya neden olabilir mi, yoksa güvenli olur mu? İkincisini varsayacağım, çünkü işletim sisteminin tüm belleğini dağıtan işletim sistemi de mantıksal olarak onu yerleştirmeli, ancak söz konusu olduğunda hiçbir şey varsaymak istemiyorum new.
Justin Time - Monica'yı

4
@JustinTime Programın ömrü boyunca kalacak dinamik olarak ayrılmış nesnelerin belleğini boşaltmak konusunda endişelenmenize gerek yoktur. Bir program yürütüldüğünde, işletim sistemi bunun için bir fiziksel bellek veya Sanal Bellek atlası oluşturur. Sanal bellek alanındaki her adres, fiziksel belleğin bir adresiyle eşleştirilir ve program çıktığında, sanal belleğe eşlenen tüm öğeler serbest bırakılır. Bu nedenle, program tamamen çıktığı sürece, ayrılmış belleğin asla silinmemesi konusunda endişelenmenize gerek yoktur.
Aiman ​​Al-Eryani

75

Tarafından oluşturulan nesneler neweninde sonunda deletesızıntı yapmalıdır . Yıkıcı çağrılmayacak, hafıza serbest bırakılmayacak. C ++ 'da çöp toplama olmadığından, bu bir sorundur.

Değer (yani yığın üzerinde) tarafından oluşturulan nesneler kapsam dışına çıktığında otomatik olarak ölür. Yıkıcı çağrısı derleyici tarafından eklenir ve işlev döndüğünde bellek otomatik olarak serbest bırakılır.

Akıllı işaretçiler gibi unique_ptr, shared_ptrsarkan referans sorunu çözmek, ancak kodlama disiplin gerektiren ve olası diğer sorunların (copyability, referans döngüler, vs.) var.

Ayrıca, çok iş parçacıklı çok senaryolarda, iş parçacıkları newarasındaki bir çekişme noktasıdır; aşırı kullanım için bir performans etkisi olabilir new. Yığın nesnesi oluşturma, her bir iş parçacığının kendi yığını olduğu için tanım yerel-yereldir.

Değer nesnelerinin dezavantajı, anasistem işlevi döndükten sonra ölmesidir - yalnızca kopyalayarak, döndürerek veya değere göre hareket ederek arayana geri gönderemezsiniz.


9
+1. Re "tarafından oluşturulan nesneler neweninde sonunda deletesızdırmak gerekir." - daha da kötüsü, new[]tarafından eşleştirilmesi gerekir delete[]ve eğer delete new[]bellek veya delete[] new-ed bellek kullanıyorsanız , tanımlanmamış davranışlar elde edersiniz - çok az derleyici bunu uyarır (Cppcheck gibi bazı araçlar mümkün olduğunda yapar).
Tony Delroy

3
@TonyDelroy Derleyicinin bunu uyaramadığı durumlar vardır. Bir işlev bir işaretçi döndürürse, yeni (tek bir öğe) veya yeni [] ise oluşturulabilir.
fbafelipe

32
  • C ++ kendi başına herhangi bir bellek yöneticisi kullanmaz. C # gibi diğer dillerde, Java hafızayı işlemek için çöp toplayıcıya sahiptir
  • C ++ uygulamaları genellikle belleği ayırmak için işletim sistemi yordamlarını kullanır ve çok fazla yeni / sil kullanılabilir belleği parçalayabilir
  • Herhangi bir uygulamada, bellek sıklıkla kullanılıyorsa, önceden ayırmanız ve gerekli olmadığında serbest bırakmanız önerilir.
  • Yanlış bellek yönetimi bellek sızıntılarına neden olabilir ve izlenmesi gerçekten zordur. Bu nedenle, yığın nesneleri işlev kapsamında kullanmak kanıtlanmış bir tekniktir
  • Yığın nesnelerini kullanmanın dezavantajı, geri dönme, işlevlere geçme vb. Nesnelerin birden fazla kopyasını oluşturur. Ancak akıllı derleyiciler bu durumların farkındadır ve performans için iyi optimize edilmiştir.
  • Belleğin iki farklı yerde tahsis edilmesi ve serbest bırakılması C ++ 'da gerçekten sıkıcıdır. Serbest bırakma sorumluluğu her zaman bir sorudur ve çoğunlukla bazı yaygın olarak erişilebilir işaretçiler, yığın nesneleri (mümkün olan maksimum) ve auto_ptr (RAII nesneleri) gibi tekniklere güveniriz.
  • En iyi şey, bellek üzerinde kontrol sahibi olmanız ve en kötü şey, uygulama için yanlış bir bellek yönetimi kullanırsak, bellek üzerinde herhangi bir kontrolünüz olmamasıdır. Bellek bozulmalarından kaynaklanan çökmeler en açık ve izlemesi zordur.

5
Aslında, bellek ayıran herhangi bir dilde, c dahil olmak üzere bir bellek yöneticisi bulunur. Çoğu sadece çok basittir, yani int * x = malloc (4); int * y = malloc (4); ... ilk çağrı hafızayı tahsis eder, diğer bir deyişle hafızadan os istemek, (genellikle 1k / 4k boyutlarında), böylece ikinci çağrı aslında hafızayı tahsis etmez, ancak size tahsis edilen son parçanın bir parçasını verir. IMO, çöp toplayıcıları bellek yöneticileri değildir, çünkü yalnızca belleğin otomatik olarak yeniden yerleştirilmesini yönetir. Bir bellek yöneticisi olarak adlandırılmak için, sadece yeniden yerleştirmeyi değil aynı zamanda bellek tahsisini de ele almalıdır.
Rahly

1
Yerel değişkenler yığın kullanır, böylece derleyici malloc()gerekli belleği ayırmak için çağrı ya da arkadaşlarına çağrı yaymaz . Ancak, yığın yığındaki herhangi bir öğeyi serbest bırakamaz, yığın hafızasının serbest bırakılmasının tek yolu yığının tepesinden çözülmesidir.
Mikko Rantalainen

C ++ "işletim sistemi yordamlarını kullanmaz"; bu dilin bir parçası değil, sadece ortak bir uygulama. C ++ herhangi bir işletim sistemi olmadan da çalışıyor olabilir.
einpoklum

22

Mümkün olduğunca az yeniyi yapmanın birkaç önemli nedeninin kaçırıldığını görüyorum:

Operatör new, deterministik olmayan bir yürütme süresine sahip

Arama newişlemi, işletim sisteminin işleminize yeni bir fiziksel sayfa tahsis etmesine neden olabilir veya etmeyebilir, sık sık yaparsanız bu oldukça yavaş olabilir. Ya da zaten uygun bir hafıza konumuna sahip olabilir, bilmiyoruz. Programınızın tutarlı ve öngörülebilir bir yürütme süresine sahip olması gerekiyorsa (gerçek zamanlı bir sistemde veya oyun / fizik simülasyonunda olduğu gibi) newzaman kritik döngülerinizden kaçınmanız gerekir .

Operatör new, örtük bir evre senkronizasyonudur

Evet beni duydunuz, işletim sisteminizin sayfa tablolarınızın tutarlı olduğundan emin olması gerekiyor ve bu tür çağrılar newiş parçacığınızın örtük bir muteks kilidi almasına neden olacaktır. Sürekli newolarak birçok iş parçacığından arıyorsanız, iş parçacıklarınızı seri hale getiriyorsunuz (bunu 32 CPU ile yaptım, her biri newbirkaç yüz bayt almak için vuruyor , ah!

Yavaş, parçalanma, hataya eğilimli, vb. Gibi geri kalanlar diğer cevaplar tarafından zaten belirtilmiştir.


2
Her ikisi de yeni yerleştirme / silme ve hafızayı önceden ayırma yoluyla önlenebilir. Ya da belleği kendiniz tahsis edebilir / boşaltabilir ve ardından yapıcı / yıkıcıyı çağırabilirsiniz. Std :: vector genellikle bu şekilde çalışır.
rxantos

1
@rxantos Lütfen OP'yi okuyun, bu soru gereksiz bellek ayırmalarından kaçınmakla ilgilidir. Ayrıca, yerleşim silme işlemi yoktur.
Emily L.

@Emily Bu OP demek istediğini düşünüyorum:void * someAddress = ...; delete (T*)someAddress
xryl669

1
Yığın kullanımı da yürütme zamanında belirleyici değildir. Aramadığınız mlock()veya benzer bir şey olmadığı sürece . Bunun nedeni, sistemin belleğinin azalmış olması ve yığın için hazır fiziksel bellek sayfaları bulunmamasıdır, bu nedenle işletim sisteminin yürütme işlemine başlamadan önce diske bazı önbellekleri (temiz kirli bellek) takas etmesi veya yazması gerekebilir.
Mikko Rantalainen

1
@Mikkorantalainen bu teknik olarak doğrudur, ancak düşük bellek durumunda, diske doğru ittiğiniz için tüm bahisler zaten wrt performansı altındadır, böylece yapabileceğiniz hiçbir şey yoktur. Zaten makul olduğunda yeni çağrılardan kaçınmak için tavsiyeyi geçersiz kılmaz.
Emily

21

Ön C ++ 17:

Çünkü sonucu akıllı bir göstergeye sarsanız bile ince sızıntılara eğilimlidir .

Nesneleri akıllı işaretçilerle sarmayı hatırlayan "dikkatli" bir kullanıcı düşünün:

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

Bu kod tehlikelidir, çünkü ikisinden birinin veya daha önce oluşturulduğunun garantisi yoktur . Bu nedenle, biri başarılı olduğunda veya diğerinden sonra başarısız olursa, ilk nesne sızdırılacaktır, çünkü yok etmek ve yeniden yerleştirmek için mevcut değildir.shared_ptrT1T2new T1()new T2()shared_ptr

Çözüm: kullanın make_shared.

Post-C ++ 17:

Bu artık bir sorun değildir: C ++ 17, bu işlemlerin sırasına bir kısıtlama getirir, bu durumda, her çağrının new()hemen ardından gelen akıllı işaretçinin yapımını takip etmesini ve aralarında başka bir işlem yapılmamasını sağlar. Bu, ikinci new()çağrıldığında, ilk nesnenin akıllı işaretçisine zaten sarılmış olduğu garanti edilir, böylece bir istisna atılırsa sızıntıları önler.

C ++ 17 tarafından sunulan yeni değerlendirme sırasının daha ayrıntılı bir açıklaması Barry tarafından başka bir cevapta verilmiştir .

Sayesinde @Remy Lebeau bu olduğuna işaret için hala : (daha az olsa da) C ++ 17 altında bir sorun shared_ptryapıcısı o silinmez için bu durumda işaretçi geçti onun kontrol bloğu tahsis ve atmak başarısız olabilir.

Çözüm: kullanın make_shared.


5
Diğer çözüm: Asla her satıra birden fazla nesne ayırmayın.
Antimon

3
@Antimony: Evet, zaten bir tane ayırdığınızda, hiç ayırmadığınız zamana kıyasla birden fazla nesne ayırmak çok daha cazip geliyor.
user541686

1
Bence daha iyi bir cevap bir istisna çağrılırsa ve hiçbir şey onu yakalamazsa smart_ptr sızıntı olacaktır.
Natalie Adams

2
C ++ 17 sonrası durumda bile, newbaşarılı shared_ptrolursa ve daha sonra inşaat başarısız olursa hala bir sızıntı olabilir . std::make_shared()bunu da çözecekti
Remy Lebeau

1
@Mehrdad, söz konusu shared_ptrkurucu paylaşılan işaretçiyi ve silmeyi saklayan bir kontrol bloğu için bellek ayırır, bu nedenle evet, teorik olarak bir bellek hatası atabilir. Yalnızca kopyalama, taşıma ve diğer adlandırma kurucuları atılamaz. make_sharedpaylaşılan nesneyi kontrol bloğunun içine ayırır, bu nedenle 2 yerine sadece 1 ayırma yapılır
Remy Lebeau

17

Büyük ölçüde, bu kendi zayıflıklarını genel bir kurala yükseltiyor. Yanlış bir şey yok se başına kullanarak nesneleri oluşturma ile newoperatöre. Bunun için bazı argümanlar var, bunu bir disiplinde yapmak zorundasınız: bir nesne oluşturursanız, imha edileceğinden emin olmanız gerekir.

Bunu yapmanın en kolay yolu, nesneyi otomatik depolamada oluşturmaktır, böylece C ++ kapsam dışına çıktığında onu yok etmeyi bilir:

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

    // do things

 }

Şimdi, son ayraçtan sonra bu bloktan düştüğünüzde fookapsam dışı olduğunu gözlemleyin . C ++ dtor'unu sizin için otomatik olarak arayacaktır. Java'nın aksine, GC'nin bulmasını beklemenize gerek yoktur.

Yazmış mıydın

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

ile açıkça eşleştirmek istersiniz

     delete foo;
  }

ya da daha iyisi, File *"akıllı işaretçi" olarak ayırmak . Eğer bu konuda dikkatli değilseniz sızıntıya neden olabilir.

Cevabın kendisi yanlış kullanmazsanız newöbek üzerinde tahsis etmediğinizi varsayar ; aslında, C ++ ile bunu bilmiyorsunuz. En fazla, bir işaretçi gibi küçük bir bellek boşluğunun kesinlikle yığına tahsis edildiğini biliyorsunuz. Ancak, Dosya uygulamasının

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

o FileImplzaman yığın üzerinde hala tahsis edilecektir.

Ve evet, sahip olduğunuzdan emin olsanız iyi olur

     ~File(){ delete fd ; }

sınıfta da; Eğer olsa bile onsuz, sen yığınından bellek sızıntısı edeceğiz görünüşte hiç yığın üzerinde ayrılamadı.


4
Referans verilen sorudaki koda bir göz atmalısınız. Bu kodda kesinlikle yanlış giden birçok şey var.
André Caron

7
Ben new kendi başına kullanmanın yanlış bir şey olduğunu kabul ediyorum , ama orijinal referansa baktığınızda yorumun atıfta bulunduğu, newistismar ediliyor. Kod, newişler yığın üzerinde olmak çok daha mantıklı olduğunda, hemen hemen her değişken için kullanılan Java veya C # gibi yazılmıştır .
Luke

5
Doğru tespit. Ancak genel tuzaklardan kaçınmak için genel kurallar uygulanır. Bu bireyin zayıflığı olsun ya da olmasın, bellek yönetimi böyle genel bir kuralı garanti edecek kadar karmaşıktır! :)
Robben_Ford_Fan_boy

9
@Charlie: Yorum yok değil kullanmak asla demek new. Bu takdirde söylüyor sahip , dinamik ayırma ve otomatik depolama arasındaki seçim, otomatik depolama kullanın.
André Caron

8
@Charlie: kullanırken yanlış bir şey yok new, ama kullanırsanız deleteyanlış yapıyorsunuz!
Matthieu M.Haziran

16

new()mümkün olduğunca az kullanılmamalıdır . Mümkün olduğunca dikkatli kullanılmalıdır . Ve pragmatizm tarafından dikte edildiği kadar sık ​​kullanılmalıdır.

Örtülü imhalarına dayanarak, yığın üzerindeki nesnelerin tahsisi basit bir modeldir. Bir nesnenin gerekli kapsamı bu modele uyuyorsa, new()ilişkili delete()ve NULL işaretçilerle kontrol edilmesine gerek yoktur . Yığın üzerinde çok sayıda kısa ömürlü nesneye sahip olduğunuz durumda, yığın parçalanması sorunlarını azaltmalıdır.

Ancak, nesnenizin ömrünün geçerli kapsamın ötesine geçmesi gerekiyorsa new(), doğru cevap budur. delete()Silinmiş nesneleri ve işaretçilerin kullanımı ile birlikte gelen diğer tüm gotcha'ları kullanarak, ne zaman ve nasıl aradığınıza ve NULL işaretçilerin olasılıklarına dikkat ettiğinizden emin olun .


9
"Nesnenizin ömrünün geçerli kapsamın ötesine geçmesi gerekiyorsa, o zaman new () doğru cevaptır" ... neden tercihli olarak değere geri dönmez veya refler constveya işaretçi tarafından arayan kapsamındaki bir değişkeni kabul etmezsiniz ?
Tony Delroy

2
@Tony: Evet, evet! Birinin referansları savunduğunu duyduğuma sevindim. Bu sorunu önlemek için yaratıldılar.
Nathan Osman

@TonyD ... veya bunları birleştirin: akıllı bir işaretçiyi değere göre döndürün. Bu şekilde arayan ve birçok durumda (yani nerede make_shared/_uniquekullanılabilirse) arayan kişinin asla newveya buna ihtiyacı yoktur delete. Bu cevap gerçek noktaları kaçırır: (A) C ++ RVO, hareket semantiği ve çıktı parametreleri gibi şeyler sağlar - bu genellikle dinamik olarak ayrılmış bellek döndürerek nesne oluşturma ve ömür boyu uzatmanın kullanılmasının gereksiz ve dikkatsiz hale geldiği anlamına gelir. (B) Dinamik ayırmanın gerekli olduğu durumlarda bile, stdlib kullanıcıyı çirkin iç detaylardan kurtaran RAII sarmalayıcılar sağlar.
underscore_d

14

Yeni kullandığınızda, nesneler yığına ayrılır. Genellikle genişlemeyi beklediğinizde kullanılır. Gibi bir nesne beyan ettiğinizde,

Class var;

yığına yerleştirilir.

Yığına yerleştirdiğiniz nesneye her zaman yeniyle yok etmeyi çağırmanız gerekir. Bu, bellek sızıntısı olasılığını açar. Yığına yerleştirilen nesneler bellek sızıntısına eğilimli değildir!


2
+1 "[yığın] genellikle genişlemeyi öngördüğünüzde kullanılır." std::stringVeya std::mapevet, keskin bir kavrayış eklemek gibi . Benim ilk tepkim "fakat aynı zamanda bir nesnenin ömrünü kodun kapsamından ayırmak" oldu, ancak gerçekten değere göre döndürmek veya arayan kapsamındaki değerleri constreferanssız veya işaretçi ile kabul etmek, bunun için "genişletme" olduğu durumlar dışında daha iyi çok. Fabrika yöntemleri gibi diğer bazı ses kullanımları .... gerçi var
Tony Delroy

12

Yığının aşırı kullanılmasından kaçınmanın önemli bir nedeni performanstır - özellikle C ++ tarafından kullanılan varsayılan bellek yönetimi mekanizmasının performansını içerir. Tahsis önemsiz durumda oldukça hızlı olsa da, katı bir düzen olmadan düzgün olmayan boyutta nesneler üzerinde newve deleteüzerinde çok fazla şey yapmak sadece bellek parçalanmasına yol açmaz, aynı zamanda tahsis algoritmasını da karmaşıklaştırır ve bazı durumlarda performansı kesinlikle yok edebilir.

Bu, hafızanın çözmek için yaratıldığı, geleneksel yığın uygulamalarının doğal dezavantajlarını azaltmaya izin verirken, yine de yığını gerektiği gibi kullanmanıza izin veren sorun.

Yine de, sorunu tamamen önlemek için daha iyi. Yığının üzerine koyabilirseniz, yapın.


Her zaman makul miktarda büyük bellek ayırabilir ve hız sorunu varsa yeni yerleşim / sil komutunu kullanabilirsiniz.
rxantos

Bellek havuzları parçalanmayı önlemek, yeniden yerleştirmeyi hızlandırmak (binlerce nesne için bir yeniden yerleştirme) ve yeniden dağıtmayı daha güvenli hale getirmektir.
Lothar

10

Bence poster You do not have to allocate everything on theheapdeğil , demek istedi stack.

Temel olarak nesneler yığın üzerinde tahsis edilir (eğer nesne boyutu izin verirse, elbette), ayırıcı tarafından oldukça fazla çalışma içeren yığın tabanlı tahsis yerine ucuz yığın tahsisi maliyeti nedeniyle ve verbosity ekler çünkü öbekte tahsis edilen verileri yönetir.


10

Yeni "çok fazla" kullanma fikrine katılmıyorum. Orijinal posterin sistem sınıflarıyla yeni kullanımı biraz saçma olsa da. ( int *i; i = new int[9999];? Gerçekten? int i[9999];Çok daha nettir.) Bence o yorumcu keçi başlamıştı budur.

Sistem nesneleriyle çalışırken, aynı nesneye birden fazla referans almanız çok nadirdir. Değer aynı olduğu sürece, önemli olan budur. Ve sistem nesneleri genellikle bellekte fazla yer kaplamaz. (karakter başına, bir dizede bir bayt). Ve eğer yaparlarsa, kütüphaneler bu bellek yönetimini dikkate alacak şekilde tasarlanmalıdır (eğer iyi yazılmışlarsa). Bu durumlarda, (kodundaki haberlerin biri veya ikisi hariç tümü), pratik olarak anlamsızdır ve sadece hatalar ve karışıklık potansiyeli ortaya çıkarır.

Bununla birlikte, kendi sınıflarınız / nesnelerinizle çalışırken (örn. Orijinal posterin Çizgi sınıfı), bellek ayak izi, verinin kalıcılığı vb. Sorunları düşünmeye başlamanız gerekir. Bu noktada, aynı değere birden fazla referans vermek çok değerlidir - birden fazla değişkenin sadece aynı değere sahip olması değil aynı zamanda bellekteki tam olarak aynı nesneye başvurması gereken bağlantılı listeler, sözlükler ve grafikler gibi yapılara izin verir . Ancak, Line sınıfı bu gereksinimlerin hiçbirine sahip değildir. Bu yüzden orijinal poster kodunun aslında kesinlikle ihtiyacı yoktur new.


dizinin boyutunu önceden bilmediğinizde genellikle new / delete kullanılır. Elbette std :: vector sizin için yeni / delete'i gizler. Hala kullanıyorsunuz ama std :: vector ile. Bu nedenle, dizinin boyutunu bilmediğinizde ve bazı nedenlerden dolayı std :: vector (Bu küçük, ama yine de var) yükünü önlemek istediğinizde kullanılır.
rxantos

When you're working with your own classes/objects... bunu yapmak için hiçbir nedeniniz yok! Q'ların küçük bir kısmı, vasıflı kodlayıcıların konteyner tasarımının detaylarındadır. Tezat bir karartıcı oran vardır veya aktif şekilde öğretmen onlar amaçsızca tekerleği yeniden icat talepleri 'programlama' 'dersler', içinde korkunç atamaları verilir - - daha ürünlerimizi görmeden stdlib var bilmiyorum yenilere karışıklık hakkında bir tekerleğin ne olduğunu ve neden çalıştığını öğrendim . Daha fazla soyut tahsisi teşvik ederek, C ++ bizi C'nin 'bağlantılı listeyle segfault'tan' kurtarabilir; lütfen izin ver .
underscore_d

" Sistem sınıfları ile yeni orijinal Yazarın kullanım (. Biraz saçma int *i; i = new int[9999];gerçekten? int i[9999];. Çok nettir)" Evet öyle nettir ama oyun şeytanın avukatı için, tip mutlaka kötü argüman değil. 9999 element ile, 9999 elementler için yeterli yığını olmayan sıkı bir gömülü sistemin hayal edebiliyorum: 9999x4 bayt ~ 40 kB, x8 ~ 80 kB'dir. Bu nedenle, bu tür sistemlerin alternatif bellek kullanarak uyguladıkları varsayılarak dinamik ayırma kullanmaları gerekebilir. Yine de, bu sadece dinamik ayırmayı haklı gösterebilir, değil new; Bir vectorbu durumda gerçek düzeltme olacağını
underscore_d

@Underscore_d ile aynı fikirde - bu harika bir örnek değil. Yığma aynı şekilde 40.000 veya 80.000 bayt eklemem. Aslında onları ( std::make_unique<int[]>()tabii ki) öbekte tahsis ediyorum .
einpoklum

3

İki sebep:

  1. Bu durumda gereksizdir. Kodunuzu gereksiz yere daha karmaşık hale getiriyorsunuz.
  2. Öbek üzerinde yer deleteayırır ve daha sonra hatırlamanız gerektiği anlamına gelir veya bellek sızıntısına neden olur.

2

newyeni goto.

Neden gotobu kadar revize edildiğini hatırlayın : Akış kontrolü için güçlü, düşük seviyeli bir araç olsa da, insanlar genellikle kodu takip etmeyi zorlaştıran gereksiz karmaşık şekillerde kullandılar. Ayrıca, en kullanışlı ve en kolay okunan kalıplar, yapılandırılmış programlama ifadelerinde (örneğin forveya while) kodlanmıştır ; Nihai sonuç, gotouygun yolun bulunduğu kodun oldukça nadir olmasıdır, eğer yazmak için cazipseniz goto, muhtemelen kötü şeyler yapıyorsunuzdur ( gerçekten ne yaptığınızı bilmiyorsanız).

newbenzerdir - genellikle gereksiz yere karmaşık ve okunması daha zor hale getirmek için kullanılır ve en kullanışlı kullanım kalıpları çeşitli sınıflara kodlanmıştır. Ayrıca, zaten standart sınıfların bulunmadığı yeni kullanım kalıplarını kullanmanız gerekiyorsa, bunları kodlayan kendi sınıflarınızı yazabilirsiniz!

Hatta iddia ediyorum newolduğu kötü daha gotosebebiyle çiftine ihtiyacı, newve deletetabloların.

Mesela goto, kullanmanız gerektiğini düşünürseniz new, muhtemelen kötü bir şey yapıyorsunuz - özellikle bunu, yaşamdaki amacı yapmanız gereken dinamik tahsisleri kapsüllemek olan bir sınıfın uygulanması dışında yapıyorsanız.


Ve şunu eklerdim: "Temelde ihtiyacınız yok".
einpoklum

1

Temel neden, öbek üzerindeki nesnelerin kullanımı ve yönetilmesinin basit değerlerden daha zor olmasıdır. Okunması ve bakımı kolay kod yazma, her zaman ciddi bir programcının birinci önceliğidir.

Başka bir senaryo, kullandığımız kütüphane değer semantiği sağlar ve dinamik ayırmayı gereksiz kılar. Std::stringiyi bir örnek.

Bununla birlikte, nesne yönelimli kod için, bir işaretçi kullanmak new- bunu önceden oluşturmak için kullanın - bir zorunluluktur. Kaynak yönetiminin karmaşıklığını basitleştirmek için, akıllı işaretçiler gibi olabildiğince basit hale getirmek için düzinelerce aracımız var. Nesneye dayalı paradigma veya genel paradigma, değer semantiğini üstlenir ve newtıpkı başka bir yerde belirtilen posterlerde olduğu gibi daha az veya hiç gerektirmez .

Geleneksel tasarım desenleri, özellikle GoF kitabında bahsedilenler new, tipik OO kodu oldukları için çok fazla kullanırlar .


4
Bu berbat bir cevap. For object oriented code, using a pointer [...] is a must: saçmalık . 'OO' değerini sadece küçük bir alt kümeye atıfta bulunuyorsanız, polimorfizm - ayrıca saçmalık: referanslar da işe yarıyor. [pointer] means use new to create it beforehand: özellikle saçmalık : otomatik olarak tahsis edilen nesnelere referanslar veya işaretçiler alınabilir ve polimorfik olarak kullanılabilir; beni izle . [typical OO code] use new a lot: belki eski bir kitapta, ama kimin umurunda? newMümkün olan her türlü belirsiz C ++ eschews / ham işaretçisi - ve bunu yaparak hiçbir şekilde daha az OO değildir
underscore_d

1

Yukarıdaki tüm doğru cevaplara bir nokta daha, ne tür bir program yaptığınıza bağlıdır. Örneğin Windows'da çekirdek geliştirme -> Yığın ciddi şekilde sınırlıdır ve kullanıcı modunda olduğu gibi sayfa hataları alamayabilirsiniz.

Bu tür ortamlarda, yeni veya C benzeri API çağrıları tercih edilir ve hatta gereklidir.

Tabii ki, bu sadece kuralın bir istisnasıdır.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.