C ++ 'da yeni anahtar kelimeyi ne zaman kullanmalıyım?


273

Kısa bir süredir C ++ kullanıyorum ve yeni anahtar kelimeyi merak ediyorum . Basitçe, kullanmalı mıyım, kullanmamalı mıyım?

1) Yeni anahtar kelimeyle ...

MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";

2) Yeni anahtar kelime olmadan ...

MyClass myClass;
myClass.MyField = "Hello world!";

Bir uygulama açısından bakıldığında, farklı görünmüyorlar (ama eminim ki) ... Ancak, birincil dilim C # ve elbette 1. yöntem alışkın olduğum şey.

Zorluk, yöntem 1'in std C ++ sınıflarıyla kullanılması daha zor gibi görünüyor.

Hangi yöntemi kullanmalıyım?

Güncelleme 1:

Son zamanlarda kapsam dışında (yani bir işlevden döndürülüyor) gidiyordu büyük bir dizi için yığın bellek (veya ücretsiz mağaza ) için yeni anahtar kelime kullanılır . Öğelerin yarısının kapsam dışında bozulmasına neden olan yığını kullanmadan önce, yığın kullanımına geçiş, öğelerin inceliğini sağladı. Yaşasın!

Güncelleme 2:

Bir arkadaşım yakın zamanda bana newanahtar kelimeyi kullanmak için basit bir kural olduğunu söyledi ; her yazışınızda new, yazın delete.

Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.

Bu, silmeyi her zaman bir yere koymak zorunda olduğunuzdan (yani bir yıkıcıya veya başka bir şekilde kesip yapıştırdığınızda) bellek sızıntılarını önlemeye yardımcı olur.


6
Kısa cevap, onunla kurtulabileceğiniz kısa versiyonu kullanın. :)
jalf

11
Kullanım STL kapları ve benzeri akıllı işaretçileri - daima ilgili silme bir yazma göre daha iyi bir teknik std::vectorve std::shared_ptr. Bunlar sizin için newve deletesizin için yapılan aramaları sarar , böylece bellek sızıntısı olasılığı daha düşüktür. Kendinize sorun, örneğin: her zaman deletebir istisna atılabilecek her yere karşılık gelen bir şeyi koymayı hatırlıyor musunuz? El ile koymak deletedüşündüğünüzden daha zordur.
AshleysBrain

@nbolton Re: GÜNCELLEME 1 - C ++ hakkında güzel şeylerden biri, C # gibi çöp toplanan langs oysa sen, yığın Kullanıcı Tanımlı Türleri saklamak için izin vermesidir kuvvet üzerinde veri depolamak için yığın . Yığın tüketir yığın verileri saklamak daha fazla kaynak verileri saklamak , böylece tercih etmeliyiz yığını için yığın sizin UDT, veri depolamak için hafıza büyük miktarda gerektirdiğinde hariç. (Bu aynı zamanda nesnelerin varsayılan olarak değere iletildiği anlamına gelir). Probleminize daha iyi bir çözüm , diziyi referans olarak işleve geçirmektir .
Charles Addis

Yanıtlar:


304

Yöntem 1 (kullanarak new)

  • Serbest depodaki nesne için bellek ayırır (Bu genellikle yığınla aynı şeydir )
  • deleteNesnenizi daha sonra açıkça belirtmenizi gerektirir . (Silmezseniz, bellek sızıntısı oluşturabilirsiniz)
  • Bellek siz alana kadar ayrılmış durumda kalır delete. (yani returnkullanarak oluşturduğunuz bir nesneyi olabilir new)
  • İşaretçi d olmadığı sürece, sorudaki örnek bellek sızdırırdelete ; ve hangi kontrol yolunun alındığına veya istisnaların atılmasına bakılmaksızın her zaman silinmelidir .

Yöntem 2 (kullanılmıyor new)

  • Yığın üzerindeki nesne için bellek ayırır (tüm yerel değişkenlerin gittiği yerde) Yığın için genellikle daha az bellek vardır; çok fazla nesne ayırırsanız, yığın taşması riskiyle karşı karşıya kalırsınız.
  • Daha deletesonra ihtiyacınız olmayacak .
  • Bellek artık kapsam dışına çıktığında ayrılmaz. (örneğin return, yığındaki bir nesneye işaretçi olmamalısınız )

Hangisini kullanacağımıza göre; yukarıdaki kısıtlamalar göz önüne alındığında, sizin için en uygun yöntemi seçersiniz.

Bazı kolay durumlar:

  • Arama konusunda endişelenmek istemiyorsanız delete(ve bellek sızıntılarına neden olma potansiyeli ) kullanmamalısınız new.
  • Bir işlevden nesnenize bir işaretçi döndürmek istiyorsanız, new

4
Bir nitpick - Ben malloc "yığın" dan ayırırken, yeni operatörün "serbest mağaza" dan bellek ayırmak inanıyorum. Pratikte genellikle olmasına rağmen, bunların aynı şey olduğu garanti edilmez. Bkz. Gotw.ca/gotw/009.htm .
Fred Larson

4
Sanırım cevabınız hangisinin kullanılacağı konusunda daha açık olabilir. (Zamanın% 99'u, seçim basittir. Yapıcı / yıkıcıda yeni / silme çağıran bir sarıcı nesnede yöntem 2'yi kullanın)
jalf

4
@jalf: Yöntem 2, yeniyi kullanmayan yöntemdir: - / Her durumda, Yöntem 2'yi (yenisi olmayan olanı) kullanarak kodlamanızın çok daha basit olacağı (örn. hata durumlarını ele alma)
Daniel LeCheminant

Başka bir nitpick ... Nick'in ilk örneğinin hafıza sızdırdığını, ikincisinin de istisnalar karşısında bile daha açık olmasını sağlamalısınız.
Arafangion

4
@Fred, Arafangion: Anlayışınız için teşekkürler; Yorumlarınızı cevaba dahil ettim.
Daniel LeCheminant

118

İkisi arasında önemli bir fark vardır.

Her şey ile ayrılan değil newçok C # değer türleri gibi davranır (ve insanlar genellikle bu cisimler muhtemelen en yaygın / bariz bir durumdur yığını, tahsis değil, her zaman doğru olduğunu söylüyorlar. Daha doğrusu, tahsis nesneleri kullanmadan newsahip otomatik depolama duration Ayrılan her şey newöbek üzerinde ayrılır ve C # işaretindeki referans türleri gibi ona bir işaretçi döndürülür.

Yığına ayrılan her şeyin derleme zamanında belirlenmiş sabit bir boyutu olmalıdır (derleyici yığın işaretçisini doğru ayarlamalıdır veya nesne başka bir sınıfın üyesiyse, diğer sınıfın boyutunu ayarlamalıdır) . Bu yüzden C # 'daki diziler referans tipleridir. Olmalıdırlar, çünkü referans türleriyle çalışma zamanında ne kadar bellek isteyeceğine karar verebiliriz. Aynı şey burada da geçerlidir. Yalnızca sabit boyutlu diziler (derleme zamanında belirlenebilen bir boyut) otomatik depolama süresi (yığın üzerinde) ile ayrılabilir. Dinamik boyutta diziler, çağrılarak yığın üzerine tahsis edilmelidir new.

(Ve işte C # ile benzerlik durur)

Şimdi, yığına ayrılan her şeyin "otomatik" depolama süresi vardır (bir değişkeni aslında olarak bildirebilirsiniz auto, ancak başka bir depolama türü belirtilmediğinde varsayılan değerdir, bu nedenle anahtar kelime pratikte gerçekten kullanılmaz, ancak burada gelen)

Otomatik saklama süresi tam olarak nasıl göründüğü anlamına gelir, değişkenin süresi otomatik olarak ele alınır. Buna karşılık, öbekte tahsis edilen herhangi bir şey sizin tarafınızdan manuel olarak silinmelidir. İşte bir örnek:

void foo() {
  bar b;
  bar* b2 = new bar();
}

Bu işlev dikkate değer üç değer oluşturur:

Hat 1 üzerinde, bir değişken bildirir bÇeşidi baryığın (otomatik süresi) hakkında.

2. satırda , yığın üzerinde bir barişaretçi b2(otomatik süre) bildirir ve yeni bir çağrı baryapar ve öbek üzerinde bir nesne tahsis eder. (dinamik süre)

İşlev döndüğünde, aşağıdakiler gerçekleşir: Birincisi, b2kapsam dışına çıkar (imha sırası her zaman inşaat sırasının tersidir). Ama b2sadece bir işaretçi, bu yüzden hiçbir şey olmuyor, kapladığı bellek basitçe serbest bırakılıyor. Ve daha önemlisi, hafıza işaret ( baröbek üzerinde örneğin) dokundu DEĞİLDİR. Yalnızca imleç serbest bırakılır, çünkü yalnızca imlecin otomatik süresi vardır. İkincisi, bkapsam dışına çıkar, bu nedenle otomatik süresine sahip olduğundan, yıkıcısı çağrılır ve bellek boşaltılır.

Ve baröbekteki örnek? Muhtemelen hala oradadır. Kimse onu silmek için uğraşmadı, bu yüzden hafızayı sızdırdık.

Bu örnekten, otomatik süreye sahip herhangi bir şeyin , yıkıcısının kapsam dışına çıktığında çağrılmasını garanti ettiğini görebiliriz . Bu yararlı. Ancak öbekte tahsis edilen her şey, ihtiyacımız olduğu sürece sürer ve dizilerdeki gibi dinamik olarak boyutlandırılabilir. Bu da faydalı. Bunu bellek ayırmalarımızı yönetmek için kullanabiliriz. Ya Foo sınıfı, yapıcısında öbek üzerinde bir miktar bellek ayırdıysa ve bu belleği yıkıcısında sildiyse ne olurdu. Daha sonra, her iki dünyanın en iyisini, tekrar serbest bırakılması garanti edilen güvenli bellek ayırmalarını elde edebiliriz, ancak her şeyi yığının üzerinde olmaya zorlama sınırlamaları olmadan.

Ve çoğu C ++ kodu tam olarak nasıl çalışır. std::vectorÖrneğin standart kütüphaneye bakın . Bu genellikle yığına tahsis edilir, ancak dinamik olarak boyutlandırılabilir ve yeniden boyutlandırılabilir. Ve bunu hafızanın içerisine belleği gerektiği gibi ayırarak yapar. Sınıfın kullanıcısı bunu asla görmez, bu nedenle bellek sızıntısı veya ayırdığınız şeyi temizlemeyi unutma şansı yoktur.

Bu ilke RAII (Kaynak Edinimi Başlatma'dır) olarak adlandırılır ve edinilmesi ve serbest bırakılması gereken herhangi bir kaynağa genişletilebilir. (ağ soketleri, dosyalar, veritabanı bağlantıları, senkronizasyon kilitleri). Hepsi kurucuda edinilebilir ve yıkıcıda serbest bırakılabilir, böylece elde ettiğiniz tüm kaynakların tekrar serbest bırakılacağı garanti edilir.

Genel bir kural olarak, asla yeni / delete komutunu doğrudan doğrudan üst düzey kodunuzdan kullanmayın. Her zaman hafızayı sizin için yönetebilecek ve tekrar boşaltılmasını sağlayacak bir sınıfa sarın. (Evet, bu kuralın istisnaları olabilir. Özellikle, akıllı işaretçiler newdoğrudan aramanızı ve işaretçiyi yapıcısına geçirmenizi gerektirir , bu da daha sonra devralıp deletedoğru bir şekilde çağrılmasını sağlar. Ancak bu hala çok önemli bir başparmak kuralıdır. )


2
"Yeni ile ayrılmayan her şey yığına yerleştirilir" Üzerinde çalıştığım sistemlerde değil ... genellikle başlatılmış (ve uninit.) Global (statik) veriler kendi segmentlerine yerleştirilir. Örneğin, .data, .bss, vb. Bağlayıcı segmentleri. Bilgiçlik, biliyorum ...
Dan

Tabii ki haklısın. Gerçekten statik veri düşünmüyordum. Tabii ki kötüyüm. :)
jalf

2
Yığına ayrılan her şeyin neden sabit bir boyutu olması gerekir?
user541686

Gelmez hep bunu aşmak için birkaç yolu vardır, ama bir yığın çünkü genel durumda o yapar. Yığının üstündeyse, yeniden boyutlandırmak mümkün olabilir, ancak üstüne başka bir şey itildiğinde, her iki taraftaki nesnelerle çevrili "duvarlanır", bu yüzden gerçekten yeniden boyutlandırılamaz . Evet, her zaman sabit bir boyuta sahip olması gerektiğini söylemek bir basitleştirme, ancak temel fikri aktarıyor (ve yığın tahsislerinde çok yaratıcı olmanıza izin veren C fonksiyonları ile uğraşmayı tavsiye etmem)
jalf

14

Hangi yöntemi kullanmalıyım?

Bu neredeyse hiçbir zaman yazma tercihlerinizle değil, bağlamla belirlenir. Nesneyi birkaç yığın üzerinde tutmanız gerekiyorsa veya yığın için çok ağırsa, ücretsiz mağazada tahsis edersiniz. Ayrıca, bir nesne ayırdığınız için, belleği serbest bırakmak da sizin sorumluluğunuzdadır. Arama deleteoperatörü.

Serbest mağaza yönetimi kullanma yükünü hafifletmek için insanlar auto_ptrve gibi şeyler icat ettiler unique_ptr. Bunlara bir göz atmanızı şiddetle tavsiye ederim. Yazma sorunlarınızda bile yardımcı olabilirler ;-)


10

C ++ ile yazıyorsanız, muhtemelen performans için yazıyorsunuzdur. Yeni ve ücretsiz mağaza kullanmak, yığını kullanmaktan çok daha yavaştır (özellikle iş parçacığı kullanırken), yalnızca ihtiyacınız olduğunda kullanın.

Diğerlerinin söylediği gibi, nesnenizin işlev veya nesne kapsamının dışında yaşaması gerektiğinde, nesne gerçekten büyük olduğunda veya derleme zamanında bir dizinin boyutunu bilmediğinizde yenisine ihtiyacınız vardır.

Ayrıca, delete komutunu kullanmaktan kaçının. Bunun yerine yeni bir akıllı işaretçiye sarın. Akıllı işaretçi aramasının sizin için silinmesine izin verin.

Akıllı işaretçinin akıllı olmadığı bazı durumlar vardır. Std :: auto_ptr <> 'ı asla bir STL kabının içinde saklamayın. Kapsayıcı içindeki kopyalama işlemleri nedeniyle işaretçiyi çok erken silecektir. Başka bir durum, nesnelere gerçekten büyük bir işaretçi STL kabınız olduğunda. boost :: shared_ptr <>, referans sayılarını yukarı ve aşağı çarptığı için bir ton hız ek yüküne sahip olacaktır. Bu durumda gitmenin en iyi yolu, STL kapsayıcısını başka bir nesneye koymak ve bu nesneye kaptaki her işaretçide silme çağrısı yapacak bir yıkıcı sağlamaktır.


10

Kısa cevap: C ++ 'da bir acemi iseniz, gereken asla kullanıyor newveya deletekendiniz.

Bunun yerine, std::unique_ptrve std::make_unique(veya daha az sıklıkla std::shared_ptrve std::make_shared) gibi akıllı işaretçiler kullanmalısınız . Bu şekilde, neredeyse bellek sızıntıları hakkında endişelenmenize gerek kalmaz. Ve daha gelişmiş olsanız bile, en iyi uygulama genellikle kullandığınız özel yöntemi newve deleteyalnızca nesne yaşam döngüsü sorunlarına adanmış küçük bir sınıfa (özel bir akıllı işaretçi gibi) kapsüllemektir .

Tabii ki, sahnelerin arkasında, bu akıllı işaretçiler hala dinamik ayırma ve yeniden konumlandırma gerçekleştiriyorlar, bu yüzden bunları kullanan kod hala ilişkili çalışma zamanı ek yüküne sahip olacaktı. Buradaki diğer yanıtlar bu sorunları ve akıllı işaretçilerin ne zaman yığın üzerinde kullanılacağına veya bunları bir nesnenin doğrudan üyeleri olarak birleştirmeye karşı ne zaman tekrarlamayacağım kadar tasarım kararları vermeyi kapsıyor. Ancak yönetici özetim şu olacaktır: bir şey sizi zorlayana kadar akıllı işaretçiler veya dinamik ayırma kullanmayın.


zaman geçtikçe bir cevabın nasıl değişebileceğini görmek ilginç;)
Wolf


2

Basit cevap evet - new () öbek üzerinde bir nesne yaratır (ömrünü yönetmek zorunda olduğunuz talihsiz yan etkisi ile (açıkça silme çağrısı yaparak), ikinci form akımda yığında bir nesne oluşturur kapsam ve bu nesne kapsam dışına çıktığında yok edilecektir.


1

Değişkeniniz yalnızca tek bir işlev bağlamında kullanılıyorsa, bir yığın değişkeni, yani Seçenek 2'yi kullanmanız daha iyi olur. Diğerlerinin söylediği gibi, yığın değişkenlerinin ömrünü yönetmeniz gerekmez; bunlar, otomatik olarak yok edilir. Ayrıca, öbek üzerinde bir değişkenin tahsis edilmesi / serbest bırakılması karşılaştırma ile yavaştır. İşleviniz yeterince sık çağrılırsa, yığın değişkenlerine karşı yığın değişkenleri kullanılırsa büyük bir performans artışı görürsünüz.

Bununla birlikte, yığın değişkenlerinin yetersiz olduğu birkaç açık örnek vardır.

Yığın değişkeninin geniş bir bellek alanı varsa, yığının taşma riski vardır. Varsayılan olarak, Windows'ta her bir iş parçacığının yığın boyutu 1 MB'dir . 1 MB boyutunda bir yığın değişkeni oluşturmanız olası değildir, ancak yığın kullanımının birikimli olduğunu aklınızda bulundurmanız gerekir. İşleviniz başka bir işlevi çağıran başka bir işlevi çağıran bir işlevi çağırırsa ... ..., bu işlevlerin tümündeki yığın değişkenleri aynı yığın üzerinde yer kaplar. Yinelemenin işlevleri, yinelemenin ne kadar derin olduğuna bağlı olarak bu soruna hızlı bir şekilde girebilir. Bu bir sorunsa, yığının boyutunu artırabilir (önerilmez) veya yeni operatörü kullanarak (önerilen) öbek üzerindeki değişkeni ayırabilirsiniz.

Diğer, daha olası bir koşul, değişkeninizin fonksiyonunuzun kapsamı dışında "yaşaması" gerektiğidir. Bu durumda, değişkeni öbek üzerinde belirli bir işlevin kapsamı dışında erişilebilecek şekilde tahsis edersiniz.


1

MyClass işlevini bir işlevin dışına mı aktarıyorsunuz veya bu işlevin dışında olmasını mı bekliyorsunuz? Diğerlerinin söylediği gibi, yığın üzerinde tahsis etmediğinizde her şey kapsamla ilgilidir. İşlevden ayrıldığınızda, gider (sonunda). Yeni başlayanlar tarafından yapılan klasik hatalardan biri, bir işlevde bir sınıfın yerel bir nesnesini oluşturma ve yığın üzerine tahsis etmeden geri döndürme girişimidir. Önceki günlerde c ++ yapıyor bu tür bir şey hata ayıklama hatırlıyorum.


0

İkinci yöntem, bildirilen bir şey intve işleve iletilen parametrelerin listesi gibi şeylerle birlikte yığında örneği oluşturur .

İlk yöntem , yığındaki bir işaretçi için yer açar; bu, bellekte MyClassyığınta veya boş bir mağazada yeni bir tahsis edilen konuma ayarlar .

İlk yöntem aynı zamanda deletesizin yarattığınız şeyi de gerektirirken new, ikinci yöntemde sınıf kapsam dışına çıktığında (genellikle bir sonraki kapanış ayracı) otomatik olarak imha edilir ve serbest bırakılır.


-1

Kısa cevap evet "yeni" anahtar kelime inanılmaz derecede önemlidir, kullandığınızda nesne verileri yığın yerine yığın üzerinde depolanır, ki bu en önemli!

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.