C ++ Nesne Örnekleme


113

Ben C ++ 'yı anlamaya çalışan bir C programcısıyım. Çoğu eğitici, aşağıdaki gibi bir kod parçacığı kullanarak nesne somutlaştırmayı gösterir:

Dog* sparky = new Dog();

bu, daha sonra yapacağınız anlamına gelir:

delete sparky;

bu mantıklı. Şimdi, dinamik bellek tahsisinin gereksiz olduğu durumda, yukarıdakileri kullanmak yerine herhangi bir sebep var mı?

Dog sparky;

Sparky kapsam dışına çıktığında yıkıcı çağrılsın mı?

Teşekkürler!

Yanıtlar:


166

Aksine, genel bir kural olarak kullanıcı kodunuzda hiçbir zaman yeni / silinmemesi gereken ölçüde, yığın tahsislerini her zaman tercih etmelisiniz.

Dediğiniz gibi, değişken yığın üzerinde bildirildiğinde, yıkıcısı kapsam dışına çıktığında otomatik olarak çağrılır, bu da kaynak ömrünü izlemek ve sızıntıları önlemek için ana aracınızdır.

Dolayısıyla, genel olarak, bellek (yeni arayarak), dosya tanıtıcıları, soketler veya başka herhangi bir şey olsun, bir kaynağı ayırmanız gerektiğinde, onu kurucunun kaynağı aldığı ve yıkıcının serbest bıraktığı bir sınıfa sarın. Ardından, yığın üzerinde bu türden bir nesne oluşturabilirsiniz ve kaynağınızın kapsam dışına çıktığında serbest kalacağı garanti edilir. Bu şekilde, bellek sızıntılarından kaçınmanızı sağlamak için yeni / sil çiftlerinizi her yerde izlemeniz gerekmez.

Bu deyimin en yaygın adı RAII'dir.

Ayrıca, özel bir RAII nesnesinin dışında yeni bir şey ayırmanız gerektiğinde, ortaya çıkan işaretçileri nadir durumlarda sarmak için kullanılan akıllı işaretçi sınıflarına bakın. Bunun yerine işaretçiyi bir akıllı işaretçiye iletirsiniz, bu daha sonra ömrünü örneğin referans sayma yoluyla izler ve son referans kapsam dışına çıktığında yıkıcıyı çağırır. Standart kitaplık, std::unique_ptrbasit kapsam tabanlı yönetime sahiptir ve std::shared_ptrpaylaşılan sahipliği uygulamak için saymaya başvurur.

Birçok eğitici, ... gibi bir parçacığı kullanarak nesne somutlaştırmayı gösterir.

Öyleyse keşfettiğiniz şey, çoğu öğreticinin berbat olduğu. ;) Öğreticilerin çoğu, gerekli olmadığında değişkenler oluşturmak için new / delete çağırmak ve ayırmalarınızın ömrünü takip etmekte zorlanmanıza neden olmak dahil olmak üzere size kötü C ++ uygulamaları öğretir.


Ham işaretçiler, auto_ptr benzeri anlambilim (sahipliğin aktarılması) istediğinizde, ancak atılmayan takas işlemini koruduğunuzda ve referans saymanın ek yükünü istemediğinizde kullanışlıdır. Uç durum belki, ama kullanışlı.
Greg Rogers

2
Bu doğru bir cevap, ancak yığın üzerinde nesne yaratma alışkanlığına asla giremememin nedeni, o nesnenin ne kadar büyük olacağı asla tam olarak belli olmamasıdır. Sadece bir yığın taşması istisnası istiyorsunuz.
dviljoen

3
Greg: Kesinlikle. Ama dediğiniz gibi, uç durum. Genel olarak, işaretçilerden kaçınılması en iyisidir. Ama bir nedenden ötürü dilde konuşuyorlar, bunu inkar etmek yok. :) dviljoen: Nesne büyükse, onu yığın üzerinde tahsis edilebilen ve yığın tahsisli verilere bir işaretçi içeren bir RAII nesnesine sararsınız.
jalf

3
@dviljoen: Hayır değilim. C ++ derleyicileri nesneleri gereksiz yere şişirmez. Göreceğiniz en kötüsü, tipik olarak dört baytın en yakın katına yuvarlanmasıdır. Genellikle, işaretçi içeren bir sınıf, işaretçinin kendisi kadar yer kaplar, bu nedenle yığın kullanımında size hiçbir maliyeti yoktur.
jalf

20

Yığın üzerinde bir şeyler olması, tahsis ve otomatik serbest bırakma açısından bir avantaj olsa da, bazı dezavantajları vardır.

  1. Yığın üzerinde büyük nesneler ayırmak istemeyebilirsiniz.

  2. Dinamik gönderim! Bu kodu düşünün:

#include <iostream>

class A {
public:
  virtual void f();
  virtual ~A() {}
};

class B : public A {
public:
  virtual void f();
};

void A::f() {cout << "A";}
void B::f() {cout << "B";}

int main(void) {
  A *a = new B();
  a->f();
  delete a;
  return 0;
}

Bu "B" yazacaktır. Şimdi Stack kullanırken ne olduğunu görelim:

int main(void) {
  A a = B();
  a.f();
  return 0;
}

Bu, Java veya diğer nesne yönelimli dillere aşina olanlar için sezgisel olmayabilecek "A" yazdıracaktır. Bunun nedeni, Bartık bir örneğe bir işaretçinizin olmamasıdır . Bunun yerine, bir örneği Boluşturulur ve atürün değişkenine kopyalanır A.

Özellikle C ++ 'da yeniyseniz, bazı şeyler beklenmedik bir şekilde gerçekleşebilir. C'de işaretçileriniz var ve hepsi bu. Onları nasıl kullanacağınızı biliyorsunuz ve HER ZAMAN aynı şeyi yapıyorlar. C ++ 'da durum böyle değildir. Tıpkı bir yöntem için bağımsız değişken olarak bu örnekte bir kullandığınızda, ne olduğunu hayal - şeyler daha komplike olsun ve eğer çok büyük bir fark ETMEZ atiptedir Aveya A*hatta A&(çağrı bazında referans). Birçok kombinasyon mümkündür ve hepsi farklı davranır.


2
-1: değer semantiğini anlayamayan insanlar ve polimorfizmin bir referans / işaretçiye ihtiyaç duyduğu (nesne dilimlemesini önlemek için) basit gerçeği, dille ilgili herhangi bir sorun oluşturmaz. Bazı insanlar onun temel kurallarını öğrenemediği için c ++ 'ın gücü bir dezavantaj olarak görülmemelidir.
underscore_d

Söylediğin için teşekkürler. İşaretçi veya referans yerine nesneyi alma yöntemiyle ve ne kadar karmaşa olduğu konusunda benzer bir sorun yaşadım. Nesnenin içinde işaretçiler var ve bu başa çıkma nedeniyle çok erken silindi.
BoBoDev

@underscore_d Kabul ediyorum; bu cevabın son paragrafı kaldırılmalıdır. Bu yanıtı düzenlediğim gerçeğini kabul ettiğim anlamına gelmiyor; Sadece içindeki hataların okunmasını istemedim.
TamaMcGlinn

@TamaMcGlinn kabul etti. İyi düzenleme için teşekkürler. Fikir bölümünü kaldırdım.
UniversE

13

İşaretçiyi kullanmanın nedeni, malloc ile ayrılmış C'deki işaretçileri kullanma nedeni ile tamamen aynı olacaktır: nesnenizin değişkeninizden daha uzun yaşamasını istiyorsanız!

Önleyebiliyorsanız, yeni operatörü KULLANMAMANIZ bile şiddetle tavsiye edilir. Özellikle istisnalar kullanırsanız. Genel olarak, derleyicinin nesnelerinizi serbest bırakmasına izin vermek çok daha güvenlidir.


13

Bu anti-kalıbı, operatörün ve adresini tam olarak alamayan insanlarda gördüm. Bir işaretçi ile bir işlevi çağırmaları gerekiyorsa, her zaman öbek üzerinde ayırırlar, böylece bir işaretçi elde ederler.

void FeedTheDog(Dog* hungryDog);

Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;

Dog goodDog;
FeedTheDog(&goodDog);

Alternatif olarak: void FeedTheDog (Dog & hungryDog); Köpek köpek; FeedTheDog (köpek);
Scott Langham

1
@ScottLangham doğru, ama yapmaya çalıştığım nokta bu değildi. Bazen, örneğin isteğe bağlı bir NULL işaretçi alan bir işlevi çağırırsınız veya bu, değiştirilemeyen mevcut bir API olabilir.
Mark Ransom

7

Yığını çok önemli bir gayrimenkul olarak ele alın ve çok mantıklı kullanın. Temel pratik kural, mümkün olduğunda yığını kullanmak ve başka bir yol olmadığında yığın kullanmaktır. Nesneleri yığın üzerinde tahsis ederek aşağıdakiler gibi birçok fayda elde edebilirsiniz:

(1). İstisnalar durumunda yığının çözülmesi konusunda endişelenmenize gerek yok

(2). Yığın yöneticinizin gerektirdiğinden daha fazla alan ayırmanın neden olduğu bellek parçalanması konusunda endişelenmenize gerek yoktur.


1
Nesnenin boyutuyla ilgili bazı hususlar dikkate alınmalıdır. Yığın boyutu sınırlıdır, bu nedenle çok büyük nesneler Yığın tahsis edilmelidir.
Adam

1
@Adam Bence Naveen burada hala doğru. Yığının üzerine büyük bir nesne koyabiliyorsanız, yapın çünkü daha iyi. Muhtemelen yapamayacağın birçok neden var. Ama bence haklı.
McKay

5

Endişelenmemin tek nedeni, Dog'un artık yığın yerine yığına tahsis edilmiş olmasıdır. Yani Dog megabayt boyutundaysa, bir sorununuz olabilir,

Yeni / sil rotasına gitmeniz gerekiyorsa, istisnalara karşı dikkatli olun. Ve bu nedenle, nesne ömrünü yönetmek için auto_ptr veya boost akıllı işaretçi türlerinden birini kullanmalısınız.


1

Yığın üzerine ayırabildiğiniz zaman (yığın üzerinde) yeni bir neden yoktur (herhangi bir nedenle küçük bir yığına sahip olmadığınız ve yığını kullanmak istemediğiniz sürece).

Yığın üzerinde ayırmak istiyorsanız, standart kitaplıktan bir shared_ptr (veya onun türevlerinden birini) kullanmayı düşünebilirsiniz. Bu, shared_ptr için tüm referanslar ortadan kalktığında sizin için silme işlemini gerçekleştirecektir.


Bir örnek için pek çok neden var, cevabımı görün.
dangerousdave

Sanırım nesnenin işlevin kapsamından daha uzun yaşamasını isteyebilirsiniz, ancak OP, yapmaya çalıştıkları şeyin bu olduğunu iddia etmedi.
Scott Langham

0

Nesnenizi dinamik olarak oluşturmayı seçmenizin başka hiç kimsenin bahsetmediği ek bir neden daha var. Dinamik, yığın tabanlı nesneler, polimorfizmden .


2
Yığın tabanlı nesnelerle de polimorfik olarak çalışabilirsiniz, bu konuda yığın / ve yığın tahsisli nesneler arasında bir fark görmüyorum. Örneğin: void MyFunc () {Dog dog; Köpek besleme); } void Yem (Hayvan ve hayvan) {oto gıda = hayvan-> GetFavouriteFood (); PutFoodInBowl (gıda); } // Bu örnekte GetFavouriteFood, her hayvanın kendi uygulamasıyla geçersiz kıldığı sanal bir işlev olabilir. Bu, hiçbir yığın dahil edilmeden polimorfik olarak çalışacaktır.
Scott Langham

-1: polimorfizm yalnızca bir referans veya işaretçi gerektirir; temeldeki örneğin depolama süresinin tamamen farkında değildir.
underscore_d

@underscore_d "Evrensel bir temel sınıf kullanmak maliyet anlamına gelir: Nesnelerin polimorfik olması için yığın olarak tahsis edilmesi gerekir;" - Bjarne Stroustrup stroustrup.com/bs_faq2.html#object
dangerousdave

LOL, Stroustrup'un kendisinin söylemiş olması umurumda değil, ama bu onun için inanılmaz derecede utanç verici bir alıntı çünkü yanılıyor. Bunu kendiniz test etmek zor değil, biliyorsunuz: yığın üzerinde bazı DerivedA, DerivedB ve DerivedC somutlaştırın; Ayrıca Base için yığın tahsisli bir işaretçi somutlaştırın ve onu A / B / C ile yerleştirebileceğinizi ve polimorfik olarak kullanabileceğinizi onaylayın. Dilin orijinal yazarına ait olsalar bile, iddialara eleştirel düşünce ve Standart referans uygulayın. İşte: stackoverflow.com/q/5894775/2757035
underscore_d

Bu şekilde ifade edersek, otomatik depolama süresi olan yaklaşık 1000 polimorfik nesneden oluşan 2 ayrı aile içeren bir nesnem var. Bu nesneyi yığın üzerinde somutlaştırıyorum ve bu üyelere referanslar veya işaretçiler aracılığıyla atıfta bulunarak, üzerlerinde çok biçimliliğin tüm yeteneklerini elde ediyor. Programımdaki hiçbir şey dinamik olarak / yığın olarak tahsis edilmemiştir (yığın tahsisli sınıfların sahip olduğu kaynaklar dışında) ve nesnemin yeteneklerini bir iota ile değiştirmez.
underscore_d

-4

Visual Studio'da da aynı sorunu yaşadım. Kullanmalısınız:

yourClass-> classMethod ();

ziyade:

yourClass.classMethod ();


3
Bu soruya cevap vermiyor. Yığın üzerinde tahsis edilen nesneye (nesneye işaretçi aracılığıyla) ve yığına tahsis edilen nesneye erişmek için farklı sözdizimi kullanmak gerektiğini unutmayın.
Alexey Ivanov
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.