Neden nesnenin kendisi yerine bir işaretçi kullanmalıyım?


1602

Java arka planından geliyorum ve C ++ nesnelerle çalışmaya başladım. Ama başıma gelen bir şey, insanların genellikle nesnelerin kendilerinden ziyade nesnelere işaretçiler kullanmasıdır, örneğin bu beyan:

Object *myObject = new Object;

ziyade:

Object myObject;

Veya bir işlevi kullanmak yerine diyelim ki şöyle testFunc():

myObject.testFunc();

yazmak zorundayız:

myObject->testFunc();

Ama bunu neden bu şekilde yapmamız gerektiğini anlayamıyorum. Bellek adresine doğrudan erişebildiğimiz için bunun verimlilik ve hız ile ilgili olduğunu varsayabilirim. Haklı mıyım?


405
Bu uygulamayı sadece takip etmek yerine sorguladığınız için size övgüde bulunun. Çoğu zaman, işaretçiler aşırı kullanılır.
Luchian Grigore

120
İşaretçi kullanmak için bir neden görmüyorsanız, yapma. Nesneleri tercih edin. Ham işaretçilerden önce unique_ptr öncesi paylaşılan_ptr öncesi nesneleri tercih edin.
stefan

113
not: java'da (temel türler hariç) her şey bir göstericidir. bu yüzden tam tersini sormalısınız: neden basit nesnelere ihtiyacım var?
Karoly Horvath

119
Java'da işaretçilerin sözdizimi ile gizlendiğini unutmayın. C ++ 'da, bir işaretçi ile işaretçi olmayan arasındaki fark kodda açıkça belirtilir. Java her yerde işaretçiler kullanır.
Daniel Martín

216
Yakın olarak çok geniş ? Ciddi anlamda? Lütfen insanlar, bu Java ++ programlama yolunun çok yaygın olduğunu ve C ++ topluluğundaki en önemli sorunlardan biri olduğunu unutmayın . Ciddiye alınmalıdır.
Manu343726

Yanıtlar:


1574

Dinamik tahsisi çok sık görmek çok talihsiz bir durum. Bu sadece kaç tane kötü C ++ programcısı olduğunu gösterir.

Bir anlamda, bir araya toplanmış iki sorunuz var. Birincisi, dinamik ayırmayı ne zaman kullanmalıyız (kullanarak new)? İkincisi işaretçileri ne zaman kullanmalıyız?

Önemli eve götürme mesajı, her zaman iş için uygun aracı kullanmanızdır . Hemen hemen her durumda, manuel dinamik ayırma yapmak ve / veya ham işaretçiler kullanmaktan daha uygun ve daha güvenli bir şey vardır.

Dinamik ayırma

Sorunuzda, bir nesne oluşturmanın iki yolunu gösterdiniz. Temel fark, nesnenin saklama süresidir. Bir Object myObject;blok içinde yaparken , nesne otomatik depolama süresi ile oluşturulur, yani kapsam dışına çıktığında otomatik olarak yok edilir. Bunu yaptığınızda new Object(), nesnenin dinamik depolama süresi vardır, yani siz açıkça belirtene kadar canlı kalır delete. Dinamik depolama süresini yalnızca ihtiyacınız olduğunda kullanmalısınız. Yani, yapabildiğiniz her zaman otomatik depolama süresine sahip nesneler oluşturmayı tercih etmelisiniz .

Dinamik ayırmaya ihtiyaç duyabileceğiniz başlıca iki durum:

  1. Nesnenin geçerli kapsamı - bu belirli bellek konumundaki o nesnenin bir kopyasını değil - geçmesini sağlamanız gerekir. Nesneyi kopyalama / taşıma konusunda sorun yaşıyorsanız (çoğu zaman olmanız gerekir), otomatik bir nesne tercih etmelisiniz.
  2. Yığını kolayca doldurabilecek çok fazla bellek ayırmanız gerekir. Kendimizi bununla (çoğu zaman zorunda kalmamanız gerekir) endişelenmememiz güzel olurdu, çünkü gerçekten C ++ 'ın amacı dışında, ama ne yazık ki, sistemlerin gerçekliği ile uğraşmak zorundayız için geliştiriyoruz.

Kesinlikle dinamik ayırmaya ihtiyaç duyduğunuzda, onu akıllı bir işaretçide veya RAII gerçekleştiren başka bir türde (standart kaplar gibi) kapsüllemelisiniz . Akıllı işaretçiler, dinamik olarak ayrılmış nesnelerin sahiplik semantiği sağlar. Bir göz atın std::unique_ptrve std::shared_ptrörneğin. Bunları uygun şekilde kullanırsanız, kendi bellek yönetiminizi yapmaktan neredeyse tamamen kaçınabilirsiniz (Sıfırın Kuralı'na bakın ).

İşaretçiler

Bununla birlikte, dinamik ayırmanın ötesinde ham işaretçiler için daha genel kullanımlar da vardır, ancak çoğunun tercih etmeniz gereken alternatifleri vardır. Daha önce olduğu gibi, gerçekten işaretçilere ihtiyacınız olmadıkça her zaman alternatifleri tercih edin .

  1. Referans semantiğe ihtiyacınız var . Bazen bir nesneyi bir işaretçi kullanarak (nasıl ayrıldığından bağımsız olarak) iletmek istersiniz, çünkü geçtiğiniz işlevin o nesneye (bir kopyasına değil) erişmesini istersiniz. Bununla birlikte, çoğu durumda, başvuru türlerini işaretçilere tercih etmelisiniz, çünkü bu özellikle onlar için tasarlanmıştır. Bunun, yukarıdaki 1 numaralı durumda olduğu gibi, nesnenin ömrünü geçerli kapsamın ötesine uzatmakla ilgili olması gerekmediğini unutmayın. Daha önce olduğu gibi, nesnenin bir kopyasını geçirme konusunda sorun yaşıyorsanız, referans semantiğe ihtiyacınız yoktur.

  2. Çok biçimliliğe ihtiyacınız var . İşlevleri yalnızca bir işaretçi veya nesneye başvuru yoluyla polimorf olarak (bir nesnenin dinamik türüne göre) çağırabilirsiniz. İhtiyacınız olan davranış buysa, işaretçiler veya referanslar kullanmanız gerekir. Yine, referanslar tercih edilmelidir.

  3. nullptrNesne atlanırken a'nın geçirilmesine izin vererek bir nesnenin isteğe bağlı olduğunu göstermek istersiniz . Bu bir bağımsız değişkense, varsayılan bağımsız değişkenleri veya aşırı işlev yüklerini kullanmayı tercih etmelisiniz. Aksi takdirde, tercihen std::optional(C ++ 17'de tanıtılan - önceki C ++ standartlarıyla kullanım boost::optional) gibi bu davranışı kapsülleyen bir tür kullanmalısınız .

  4. Derleme süresini artırmak için derleme birimlerini ayırmak istiyorsunuz . Bir işaretçinin kullanışlı özelliği, yalnızca işaretli türün ileri bir bildirimini istemenizdir (nesneyi kullanmak için bir tanıma ihtiyacınız olacaktır). Bu, derleme işleminizin bölümlerini ayırmanıza olanak tanır, bu da derleme süresini önemli ölçüde artırabilir. Pimpl deyimine bakın .

  5. Bir C kütüphanesi veya C tarzı bir kütüphaneyle arayüz kurmanız gerekir . Bu noktada, ham işaretçiler kullanmak zorunda kalırsınız. Yapabileceğiniz en iyi şey, ham işaretçilerinizin yalnızca mümkün olan en son anda gevşemesini sağlamaktır. Akıllı bir işaretçiden ham işaretçi, örneğin getüye işlevini kullanarak alabilirsiniz . Bir kütüphane sizin için bir tutamaç aracılığıyla yeniden konumlandırmanızı beklediği bir ayırma gerçekleştirirse, tutamacı, nesneyi uygun bir şekilde serbest bırakacak özel bir silici ile tutamacı akıllı bir işaretçiye sarıp sarmalayabilirsiniz.


83
"Geçerli kapsamı aşmak için nesneye ihtiyacınız var." - Bununla ilgili ek bir not: mevcut kapsamı aşmak için nesneye ihtiyaç duyduğunuz gibi görünüyor, ancak gerçekten istemiyorsunuz. Örneğin, nesnenizi bir vektörün içine koyarsanız, nesne vektöre kopyalanır (veya taşınır) ve kapsamı sona erdiğinde orijinal nesne imha edilir.

25
Birçok yerde s / copy / move / olduğunu unutmayın. Bir nesneyi döndürmek kesinlikle bir hareket anlamına gelmez. Ayrıca, bir nesneye bir işaretçi aracılığıyla erişmenin nasıl oluşturulduğuna dik olduğunu da unutmamalısınız.
Köpek yavrusu

15
Bu cevapta RAII'ye açık bir referansı özledim. C ++, tüm (neredeyse hepsi) kaynak yönetimi ile ilgilidir ve RAII, C ++ üzerinde yapmanın yoludur (Ve ham işaretçilerin oluşturduğu ana sorun: RAII Breaking)
Manu343726

11
Akıllı işaretçiler C ++ 11'den önce vardı, örneğin boost :: shared_ptr ve boost :: scoped_ptr. Diğer projelerin kendi eşdeğerleri vardır. Sen hareket semantiği elde edemezsiniz ve std :: auto_ptr ataması kusurlu, bu nedenle C ++ 11 işleri iyileştirir, ancak tavsiye hala iyidir. (Ve üzücü bir nitpick, bir C ++ 11 derleyicisine erişim için yeterli değil , kodunuzun çalışmasını isteyebileceğiniz tüm derleyicilerin C ++ 11'i desteklemesi gerekebilir. Evet, Oracle Solaris Studio, ben size bakıyor.)
armb

7
@ MDMoore313 YazabilirsinizObject myObject(param1, etc...)
user000001

173

İşaretçiler için birçok kullanım durumu vardır.

Polimorfik davranış . Polimorfik tipler için, dilimlemeyi önlemek için işaretçiler (veya referanslar) kullanılır:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

Referans anlambilim ve kopyalamadan kaçınma . Polimorfik olmayan tipler için, bir işaretçi (veya referans) potansiyel olarak pahalı bir nesnenin kopyalanmasını önler

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

C ++ 11, pahalı nesnelerin birçok kopyasını işlev bağımsız değişkenine ve dönüş değerleri olarak önleyebilecek taşıma semantiğine sahip olduğunu unutmayın. Ancak bir işaretçi kullanmak kesinlikle bunlardan kaçınacaktır ve aynı nesne üzerinde birden fazla işaretleyiciye izin verecektir (oysa bir nesne yalnızca bir kereden taşınabilir).

Kaynak edinimi . newİşleci kullanarak bir kaynağa işaretçi oluşturmak , modern C ++ ' da bir anti-desendir . Özel bir kaynak sınıfı (Standart kapsayıcılardan biri) veya akıllı bir işaretçi ( std::unique_ptr<>veya std::shared_ptr<>) kullanın. Düşünmek:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

vs.

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

Ham bir işaretçi, doğrudan oluşturma yoluyla veya dolaylı olarak dönüş değerleri yoluyla sahiplikle herhangi bir şekilde değil, yalnızca "görünüm" olarak kullanılmalıdır. Ayrıca C ++ SSS bölümündeki bu Soru ve Cevaplara bakın .

Daha hassas yaşam süresi kontrolü Paylaşılan bir işaretçi her kopyalandığında (örneğin bir işlev argümanı olarak) işaret ettiği kaynak canlı tutulur. Normal nesneler ( newsizin tarafınızdan ya da bir kaynak sınıfının içinde tarafından oluşturulmamış ) kapsam dışına çıkarken yok edilir.


17
"Yeni işleci kullanarak bir kaynağa bir işaretçi oluşturmak bir anti-patern" Bence bir ham işaretçiye sahip olmanın bir şey karşıtı olduğunu bile düşünüyorum . Sadece yaratma değil, unique_ptr
hamil

1
@dyp tnx, güncellenmiş ve bu konudaki C ++ SSS Soru-Cevap bölümüne başvuruyor.
TemplateRex

4
Akıllı işaretçileri her yerde kullanmak bir anti-desendir. Uygulanabilir olduğu birkaç özel durum vardır, ancak çoğu zaman, dinamik ayırma (keyfi ömür) için tartışılan aynı sebep, her zamanki akıllı işaretçilerin de aleyhine tartışır.
James Kanze

2
@JamesKanze Akıllı işaretçilerin her yerde, sadece sahiplik için kullanılması gerektiğini ve aynı zamanda ham işaretçilerin sahiplik için değil, yalnızca görünümler için kullanılması gerektiğini ima etmek istedim.
TemplateRex

2
@TemplateRex hun(b)Derleme kadar yanlış tür sağladığınızı bilmiyorsanız, imza bilgisi de gerektiren biraz saçma görünüyor . Referans sorunu genellikle derleme zamanında yakalanmayacak ve hata ayıklamak için daha fazla çaba gösterecek olsa da, argümanların doğru olduğundan emin olmak için imzayı kontrol ediyorsanız, argümanlardan herhangi birinin referans olup olmadığını da görebilirsiniz. bu nedenle referans biti sorun olmayan bir şey haline gelir (özellikle seçili işlevlerin imzasını gösteren IDE'leri veya metin editörlerini kullanırken). Ayrıca const&,.
JAB

130

Bu soruya, ileri bildirimler, polimorfizm vb.

İki dili karşılaştırarak durumu inceleyelim:

Java:

Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java

object1 = object2; 
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other

Buna en yakın eşdeğer:

C ++:

Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete object1;
//Since C++ does not have a garbage collector, if we don't do that, the next line would 
//cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
//and that we have no way to reclaim...

object1 = object2; //Same as Java, object1 points to object2.

Alternatif C ++ yolunu görelim:

Object object1; //A new object is allocated on the STACK
Object object2; //Another new object is allocated on the STACK
object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
//using the "copy assignment operator", the definition of operator =.
//But, the two objects are still different. Change one, the other remains unchanged.
//Also, the objects get automatically destroyed once the function returns...

Bunu düşünmenin en iyi yolu - az ya da çok - Java'nın (dolaylı olarak) nesnelere işaretçileri işlemesi, C ++ ise nesnelere ya da nesnelerin kendilerine yönelik işaretleyicileri işlemesi. Bunun istisnaları vardır - örneğin, Java "ilkel" türlerini bildirirseniz, bunlar işaretçiler değil kopyalanan gerçek değerlerdir. Yani,

Java:

int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.

Bununla birlikte, işaretçileri kullanmak, şeyleri işlemek için doğru ya da yanlış yol olmak zorunda DEĞİLDİR; ancak diğer cevaplar bunu tatmin edici bir şekilde ele almıştır. Genel fikir, C ++ 'da nesnelerin ömrü ve nerede yaşayacakları üzerinde çok daha fazla kontrole sahip olmanızdır.

Eve dönün - Object * object = new Object()yapı aslında tipik Java (veya bu konu için C #) semantiğine en yakın olanıdır.


7
Object2 is now "dead": Sanırım demek istediğin myObject1ya da daha doğrusu the object pointed to by myObject1.
Clément

2
Aslında! Biraz yeniden sildi.
Gerasimos R

2
Object object1 = new Object(); Object object2 = new Object();çok kötü bir kod. İkinci yeni veya ikinci Nesne yapıcı fırlayabilir ve şimdi nesne1 sızdırılmıştır. Raw news kullanıyorsanız , newen kısa sürede RAII sarmalayıcılara ed nesneleri sarmalısınız.
PSkocik

8
Aslında, bu bir program olsaydı ve etrafında başka bir şey olmuyor olabilirdi. Neyse ki, bu sadece C ++ 'daki bir Pointer'ın nasıl davrandığını gösteren bir açıklama pasajı - ve RAII nesnesinin ham bir işaretçi ile değiştirilemediği birkaç yerden biri - ham işaretçiler hakkında çalışıyor ve öğreniyor ...
Gerasimos R

80

İşaretçileri kullanmanın bir başka iyi nedeni de ileri bildirimlerdir . Yeterince büyük bir projede derleme süresini gerçekten hızlandırabilirler.


7
Bu gerçekten yararlı bilgi karışımı ekliyor, bu yüzden bir cevap yaptı sevindim!
TemplateRex

3
std :: shared_ptr <T> ayrıca T'nin ileri bildirimleriyle de çalışır (std :: unique_ptr <T>
işe

13
@berkus: std::unique_ptr<T>ile ilgili ileri bildirimlerle çalışır T. Sadece yıkıcı std::unique_ptr<T>denildiğinde Ttam bir tür olduğundan emin olmanız gerekir . Bu genellikle std::unique_ptr<T>, başlık dosyasında yıkıcısını bildiren ve cpp dosyasına uygulayan sınıfınız anlamına gelir (uygulama boş olsa bile).
David Stone

Modüller bunu düzeltir mi?
Trevor Hickey

@TrevorHickey Eski yorum biliyorum, ama yine de cevap vermek. Modüller bağımlılığı ortadan kaldırmaz, ancak bağımlılığı çok ucuz, performans maliyeti açısından neredeyse ücretsiz hale getirmelidir. Ayrıca, modüllerdeki genel hızlanma derleme sürelerinizi kabul edilebilir bir aralıkta elde etmek için yeterliyse, artık bir sorun değildir.
Aidiakapi

79

önsöz

Java, hype'ın aksine, C ++ gibi bir şey değildir. Java hype makinesi, Java'nın C ++ benzeri sözdizimine sahip olduğundan, dillerin benzer olduğuna inanmanızı ister. Hiçbir şey gerçeğin ötesinde olamaz. Bu yanlış bilgi, Java programcılarının kodlarının sonuçlarını anlamadan C ++ 'a gitme ve Java benzeri sözdizimi kullanma nedeninin bir parçasıdır.

İleri gidiyoruz

Ama bunu neden bu şekilde yapmalıyız. Bellek adresine doğrudan erişebildiğimiz için bunun verimlilik ve hız ile ilgili olduğunu varsayabilirim. Haklı mıyım?

Aksine, aslında. Yığın yığına göre çok daha yavaştır , çünkü yığın yığına göre çok basittir. Otomatik depolama değişkenleri (yığın değişkenleri olarak da bilinir) kapsam dışı olduktan sonra yıkıcılarını çağırır. Örneğin:

{
    std::string s;
}
// s is destroyed here

Öte yandan, dinamik olarak ayrılmış bir işaretçi kullanırsanız, yıkıcısı manuel olarak çağrılmalıdır. deletebu yıkıcıyı sizin için çağırıyor.

{
    std::string* s = new std::string;
}
delete s; // destructor called

Bunun newC # ve Java'daki sözdizimi ile ilgisi yoktur . Tamamen farklı amaçlar için kullanılırlar.

Dinamik ayırmanın faydaları

1. Dizinin boyutunu önceden bilmek zorunda değilsiniz

Birçok C ++ programcısının karşılaştığı ilk sorunlardan biri, kullanıcılardan rasgele girdi kabul ettiklerinde, yalnızca bir yığın değişkeni için sabit bir boyut ayırabilmenizdir. Dizilerin boyutunu da değiştiremezsiniz. Örneğin:

char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow

Tabii ki, eğer bir std::stringyerine kullandıysanız , std::stringdahili olarak kendisini yeniden boyutlandırır , böylece bir sorun olmamalıdır. Ama esasen bu sorunun çözümü dinamik tahsis. Kullanıcının girişine göre dinamik bellek ayırabilirsiniz, örneğin:

int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];

Yan not : Yeni başlayanların yaptığı bir hata, değişken uzunluklu dizilerin kullanılmasıdır. Bu bir GNU uzantısı ve aynı zamanda Clang'da bir tane çünkü GCC'nin uzantılarının çoğunu yansıtıyorlar. Bu nedenle aşağıdakilere int arr[n]güvenilmemelidir.

Yığın yığından çok daha büyük olduğu için, kişinin istediği kadar hafızayı keyfi olarak tahsis edebilir / yeniden tahsis edebilirken, yığının bir sınırlaması vardır.

2. Diziler işaretçi değildir

Bu sorduğunuz bir fayda nedir? Dizilerin ve işaretçilerin arkasındaki karışıklığı / efsaneyi anladığınızda cevap netleşecektir. Çoğunlukla aynı oldukları varsayılır, ancak değildir. Bu efsane, işaretçilerin tıpkı diziler gibi abone olabileceğinden ve dizilerin işlev bildiriminde en üst düzeydeki işaretçilere bozunmasından kaynaklanmaktadır. Ancak, bir dizi bir işaretçiye bozulduğunda, işaretçi sizeofbilgilerini kaybeder . Böylece sizeof(pointer)işaretçinin boyutunu bayt cinsinden verir, bu da 64 bit sistemde genellikle 8 bayttır.

Dizilere atayamazsınız, yalnızca başlatabilirsiniz. Örneğin:

int arr[5] = {1, 2, 3, 4, 5}; // initialization 
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                             // be given by the amount of members in the initializer  
arr = { 1, 2, 3, 4, 5 }; // ERROR

Öte yandan, işaretçilerle istediğinizi yapabilirsiniz. Ne yazık ki, işaretçiler ve diziler arasındaki ayrım Java ve C # 'da el sallandığından, yeni başlayanlar farkı anlamıyor.

3. Çok Biçimlilik

Java ve C #, örneğin asanahtar kelimeyi kullanarak nesnelere başka bir şey gibi davranmanıza izin veren olanaklara sahiptir . Birisi bir Entitynesneyi bir nesne olarak ele almak isterse Player, bunu yapabilirdi Player player = Entity as Player;Bu, yalnızca belirli bir tür için geçerli olması gereken homojen bir kapta işlevleri çağırmak istiyorsanız çok kullanışlıdır. İşlevsellik aşağıda benzer bir şekilde elde edilebilir:

std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
     auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
     if (!test) // not a triangle
        e.GenericFunction();
     else
        e.TriangleOnlyMagic();
}

Diyelim ki sadece Üçgenler bir Döndürme işlevine sahip olsaydı, sınıfın tüm nesnelerinde çağırmayı denediyseniz derleyici hatası olurdu. Düğmesini kullanarak anahtar kelimeyi dynamic_castsimüle edebilirsiniz as. Açıkça belirtmek gerekirse, bir yayın başarısız olursa geçersiz bir işaretçi döndürür. Yani !testaslında testNULL olup olmadığını kontrol etmek için bir kısayol veya geçersiz bir işaretçi, yani döküm başarısız oldu.

Otomatik değişkenlerin faydaları

Dinamik ayırmanın yapabileceği tüm harika şeyleri gördükten sonra, muhtemelen neden hiç kimse dinamik ayırmayı sürekli KULLANMAYACAKSINIZ merak ediyorsunuz? Sana zaten bir nedenden bahsetmiştim, yığın yavaş. Ve tüm bu belleğe ihtiyacınız yoksa, onu kötüye kullanmamalısınız. İşte belirli bir sırayla bazı dezavantajlar:

  • Hata eğilimli. Manuel bellek ayırma tehlikelidir ve sızıntı yapmaya eğilimlisiniz. Hata ayıklayıcıyı veya valgrind(bir bellek sızıntısı aracını) kullanma konusunda yetkin değilseniz, saçınızı başınızdan çekebilirsiniz. Neyse ki RAII deyimleri ve akıllı işaretçiler bunu biraz hafifletir, ancak Üçün Kuralı ve Beşin Kuralı gibi uygulamalara aşina olmalısınız. Almak için çok fazla bilgi var ve bilmeyen ya da umursamayan yeni başlayanlar bu tuzağa düşecek.

  • Bu gerekli değil. newAnahtar kelimeyi her yerde kullanmanın deyimsel olduğu Java ve C # 'dan farklı olarak , C ++' da bunu sadece ihtiyacınız olduğunda kullanmalısınız. Ortak ifade gider, bir çekiç varsa her şey çivi gibi görünür. C ++ ile başlayan yeni başlayanlar işaretçilerden korkar ve yığın değişkenlerini alışkanlıkla kullanmayı öğrenirken, Java ve C # programcıları işaretçileri anlamadan kullanmaya başlarlar ! Bu tam anlamıyla yanlış ayağa çıkıyor. Bildiğiniz her şeyi terk etmelisiniz çünkü sözdizimi bir şeydir, dili öğrenmek başka bir şeydir.

1. (N) RVO - Aka, (Adlandırılmış) Dönüş Değeri Optimizasyonu

Birçok derleyicinin yaptığı bir optimizasyon, elision ve dönüş değeri optimizasyonu olarak adlandırılan şeylerdir . Bunlar, çok sayıda öğe içeren bir vektör gibi çok büyük nesneler için yararlı olan gereksiz kopyaları ortadan kaldırabilir. Normalde yaygın bir uygulama için işaretçileri kullanmaktır transfer sahipliği ziyade büyük nesneleri kopyalama daha hareket onları etrafında. Bu, hareket semantiği ve akıllı işaretçilerin başlamasına neden oldu .

Eğer işaretçileri kullanıyorsanız, (K) RVO gelmez DEĞİL meydana gelir. Optimizasyon konusunda endişeleniyorsanız, işaretçiler döndürmek veya geçmek yerine (N) RVO'dan faydalanmak daha yararlı ve daha az hataya açıktır. Bir fonksiyonun arayanı deletedinamik olarak tahsis edilmiş bir nesne ve benzeri şeylerden sorumluysa hata sızıntıları meydana gelebilir . İşaretçiler bir sıcak patates gibi geçiriliyorsa, bir nesnenin sahipliğini izlemek zor olabilir. Daha basit ve daha iyi olduğu için yığın değişkenlerini kullanın.


"Yani! Test aslında testin NULL olup olmadığını kontrol etmek için bir kısayol veya geçersiz bir işaretçi anlamına gelir. Bence bu cümle anlaşılır olması için yeniden yazılmalıdır.
berkus

4
"Java hype makinesi inanmanızı istiyor" - belki 1997'de, ama bu şimdi anakronistik, 2014'te Java'yı C ++ ile karşılaştırmak için artık motivasyon yok.
Matt R

15
Eski soru, ama kod segmentinde { std::string* s = new std::string; } delete s; // destructor called.... kesinlikle bu deleteişe yaramaz çünkü derleyici sartık ne olduğunu bilmiyor ?
badger5000

2
-1 VERMİYORUM, ancak açılış açıklamalarına yazılı olarak katılmıyorum. İlk olarak, herhangi bir "yutturmaca" olduğunu kabul etmiyorum - Y2K civarında olabilirdi, ama şimdi her iki dil de iyi anlaşılıyor. İkincisi, oldukça benzer olduklarını iddia ediyorum - C ++, C Simula ile evli, Java Sanal Makine, Çöp Toplayıcı ve AĞIRLI özellikleri keser ve C #, Java için eksik özellikleri yeniden düzenler ve yeniden ekler. Evet, bu, kalıpları ve geçerli kullanımı HUGELY'yi farklı kılar, ancak farkları görebilmek için ortak altyapıyı / tasarımı anlamak faydalıdır.
Gerasimos R

1
@James Matta: Elbette hafızanın hafıza olduğunu düzeltirsiniz ve her ikisi de aynı fiziksel hafızadan ayrılır, ancak dikkate alınması gereken bir şey, yığın tahsisli nesnelerle daha iyi performans özellikleri elde etmenin çok yaygın olmasıdır. veya en azından en yüksek seviyeleri - işlevler girip çıkarken önbellekte "sıcak" olma şansı çok yüksekken, öbek böyle bir fayda sağlamaz, bu nedenle öbekte işaretçi iseniz , birden çok önbellek özlemi alabilirsiniz. Eğer büyük olasılıkla değil yığın olur. Ancak tüm bu "rastgelelik" normalde yığını destekler.
Gerasimos R

23

C ++, bir nesneyi geçirmenin üç yolunu sunar: işaretçiye, başvuruya ve değere göre. Java sizi ikincisiyle sınırlar (tek istisna int, boolean vb. Gibi ilkel türlerdir). C ++ 'ı sadece garip bir oyuncak gibi kullanmak istemiyorsanız, bu üç yol arasındaki farkı öğrenmeniz daha iyi olur.

Java, 'bunu kim ve ne zaman yok etmeli?' Diye bir sorun olmadığını iddia ediyor. Cevap: Çöp Toplayıcı, Büyük ve Korkunç. Bununla birlikte, (evet, bellek sızıntıları karşı% 100 koruma sağlayamaz java yapabilirsiniz bellek sızıntısı ). Aslında GC size yanlış bir güvenlik duygusu verir. SUV'niz ne kadar büyük olursa, tahliye cihazına giden yol o kadar uzun olur.

C ++, nesnenin yaşam döngüsü yönetimi ile yüz yüze kalmanızı sağlar. Bununla başa çıkmak için araçlar var ( akıllı işaretçiler ailesi, Qt'de QObject vb.), Ancak bunların hiçbiri GC gibi 'ateşle ve unut' şeklinde kullanılamaz: her zaman bellek işlemeyi aklınızda bulundurmalısınız. Sadece bir nesneyi yok etmekle ilgilenmemekle kalmaz, aynı nesneyi birden fazla kez yok etmekten de kaçınmalısınız.

Henüz korkmadınız mı? Tamam: döngüsel referanslar - bunları kendiniz halledin, insan. Ve unutmayın: her nesneyi bir kez tam olarak öldürün, C ++ çalışma zamanları cesetlerle uğraşanları, ölüleri yalnız bırakanları sevmiyoruz.

Yani, sorunuza geri dönelim.

Nesnenizi, işaretçiyle veya referansla değil, değere göre ilettiğinizde, nesneyi kopyalarsınız (ister birkaç bayt ister büyük bir veritabanı dökümü olsun, tüm nesne) - ikincisini önlemek için yeterince akıllısınız. t you?) her yaptığınızda '='. Nesnenin üyelerine erişmek için '.' (nokta).

Nesnenizi işaretçiyle ilettiğinizde, yalnızca birkaç bayt (32 bit sistemlerde 4, 64 bit olanlarda 8), yani bu nesnenin adresi kopyalarsınız. Ve bunu herkese göstermek için, üyelere eriştiğinizde bu süslü '->' operatörünü kullanırsınız. Veya '*' ve '.' Kombinasyonunu kullanabilirsiniz.

Referansları kullandığınızda, bir değer gibi davranan işaretçiyi alırsınız. Bu bir işaretçi, ancak üyelere '.'

Ve zihninizi bir kez daha patlatmak için: virgülle ayrılmış birkaç değişken bildirdiğinizde, (elleri izleyin):

  • Tür herkese verilir
  • Değer / işaretçi / referans değiştirici bireyseldir

Misal:

struct MyStruct
{
    int* someIntPointer, someInt; //here comes the surprise
    MyStruct *somePointer;
    MyStruct &someReference;
};

MyStruct s1; //we allocated an object on stack, not in heap

s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'

s1.someReference.someInt = 5; //now s1.someInt has value '5'
                              //although someReference is not value, it's members are accessed through '.'

MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.

//OK, assume we have '=' defined in MyStruct

s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one

1
std::auto_ptrkullanımdan kaldırıldı, lütfen kullanmayın.
Neil

2
Referans değişkenini içeren bir başlatma listesine sahip bir kurucuya sahip olmadan üye olarak referansınız olamaz. (Bir referans hemen başlatılmalıdır. Oluşturucu gövdesi bile ayarlamak için çok geç, IIRC.)
cHao

20

C ++ 'da, yığına ayrılan nesneler ( Object object;bir blok içindeki deyimi kullanarak ) yalnızca bildirildikleri kapsamda yaşarlar. Kod bloğu yürütmeyi bitirdiğinde, bildirilen nesne yok edilir. Öte yandan, belleği yığın olarak ayırırsanız, Object* obj = new Object()siz arama yapıncaya kadar yığın halinde yaşamaya devam ederler delete obj.

Nesneyi yalnızca bildirilen / ayrılan kod bloğunda kullanmak istemediğimde öbek üzerinde bir nesne oluştururdum.


6
Object objher zaman yığın üzerinde değildir - örneğin globaller veya üye değişkenler.
tenfour

2
@LightnessRacesinOrbit Küresel ve üye değişkenlerden değil, yalnızca bir blokta tahsis edilen nesnelerden bahsettim. Şey açık değildi, şimdi düzeltildi - cevaba "bir blok içinde" eklendi. Şimdi onun yanlış bilgi değil umut :)
Karthik Kalyanasundaram

20

Ama neden böyle kullanmalıyız?

Kullanırsanız, işlev gövdesi içinde nasıl çalıştığını karşılaştıracağım:

Object myObject;

Fonksiyonun içinde, myObjectbu fonksiyon geri döndüğünde imha edilirsiniz. Bu nedenle, nesnenizin işlevinizin dışında olması gerekmiyorsa yararlıdır. Bu nesne geçerli iş parçacığı yığınına yerleştirilir.

İşlev gövdesinin içine yazarsanız:

 Object *myObject = new Object;

daha sonra işaret edilen Object sınıfı örneği myObjectişlev sona erdiğinde yok olmaz ve ayırma yığın üzerindedir.

Java programcısıysanız, ikinci örnek, java altında nesne tahsisinin nasıl çalıştığına daha yakındır. Bu satır: Object *myObject = new Object;java: öğesine eşdeğerdir Object myObject = new Object();. Aradaki fark, java myObject altında çöp toplanacak, c ++ altında serbest bırakılmayacak, açıkça bir yere `` myObject '' ifadesini çağırmalısınız; aksi taktirde bellek sızıntılarına neden olacaksınız.

C ++ 11'den beri new Object, paylaşılan_ptr / unique_ptr içinde değerleri depolayarak güvenli dinamik ayırma yollarını kullanabilirsiniz:

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

ayrıca, nesneler genellikle harita veya vektör gibi kaplarda saklanır, nesnelerinizin ömrünü otomatik olarak yönetir.


1
then myObject will not get destroyed once function endsKesinlikle olacak.
Orbit'te Hafiflik Yarışları

6
İşaretçi durumunda, myObjectdiğer yerel değişkenlerin yaptığı gibi yine de yok edilecektir. Fark değeri bir olmasıdır işaretçi , bir nesneye değil, nesnenin kendisi ve aptal işaretçinin tahrip olması pointee etkilemez. Böylece nesne söz konusu yıkımdan sağ çıkacaktır.
cHao

Yerel değişkenlerin (işaretçi içeren) elbette serbest bırakılacağı düzeltildi - yığın halindeler.
marcinj

13

Teknik olarak bu bir bellek ayırma sorunudur, ancak burada bunun iki pratik yönü daha vardır. İki şeyle ilgilidir: 1) Kapsam, işaretçi olmayan bir nesneyi tanımladığınızda, içinde tanımlandığı kod bloğundan sonra artık ona erişemezsiniz, oysa "new" ile bir işaretçi tanımlarsanız, aynı işaretçide "sil" çağrılıncaya kadar bu belleğe bir işaretçiniz olan herhangi bir yerden erişebilirsiniz. 2) Bir işleve bağımsız değişkenler iletmek istiyorsanız, daha verimli olabilmek için bir işaretçi veya başvuru iletmek istersiniz. Bir Nesneyi ilettiğinizde nesne kopyalanır, eğer bu çok fazla bellek kullanan bir nesne ise bu CPU tüketebilir (örneğin veri dolu bir vektörü kopyalarsınız). Bir işaretçiyi ilettiğinizde, geçirdiğiniz tek şey bir int'tir (uygulamaya bağlı olarak, ancak çoğu bir int'tir).

Bunun dışında "yeni" bir noktada serbest bırakılması gereken yığın bellek ayırdığını anlamak gerekir. "Yeni" kullanmanız gerekmediğinde, "yığın" üzerinde normal bir nesne tanımı kullanmanızı öneririm.


6

Asıl soru şudur: Nesnenin kendisinden ziyade neden bir işaretçi kullanmalıyım? Ve cevabım, (neredeyse) asla nesne yerine işaretçi kullanmamalısınız, çünkü C ++ referansları vardır , daha sonra işaretçilerden daha güvenlidir ve işaretçilerle aynı performansı garanti eder.

Sorunuzda bahsettiğiniz başka bir şey:

Object *myObject = new Object;

O nasıl çalışır? Bu Objecttür bir işaretçi oluşturur , bir nesneye uyacak şekilde bellek ayırır ve varsayılan yapıcı çağırır, iyi geliyor, değil mi? Ama aslında o kadar iyi değil, dinamik olarak bellek ayırdıysanız (kullanılan anahtar kelime new), belleği manuel olarak da boşaltmanız gerekir, yani kodda olması gerekir:

delete myObject;

Bu yıkıcıyı çağırır ve belleği boşaltır, kolay görünür, ancak büyük projelerde bir iş parçacığının belleği serbest bırakıp bırakmadığını tespit etmek zor olabilir, ancak bu amaçla paylaşılan işaretçileri deneyebilirsiniz , bunlar performansı biraz düşürür, ancak çalışmak çok daha kolaydır onlar.


Ve şimdi bazı tanıtımlar bitti ve soruya geri dönün.

İşlevler arasında veri aktarırken daha iyi performans elde etmek için nesneler yerine işaretçiler kullanabilirsiniz.

Bir göz atın, std::string(aynı zamanda nesne) ve büyük XML gibi çok fazla veri içeriyor, şimdi ayrıştırmanız gerekiyor, ancak bunun için void foo(...)farklı şekillerde bildirilebilecek bir işleve sahipsiniz :

  1. void foo(std::string xml); Bu durumda, değişkeninizdeki tüm verileri işlev yığınına kopyalayacaksınız, biraz zaman alıyor, bu nedenle performansınız düşük olacak.
  2. void foo(std::string* xml); Bu durumda, işaretçiyi nesneye, geçiş size_tdeğişkeniyle aynı hıza geçireceksiniz , ancak bu bildirimin hataya eğilimli olması nedeniyle NULLişaretçiyi veya geçersiz işaretçiyi geçirebilirsiniz . CReferanslar olmadığı için genellikle kullanılır .
  3. void foo(std::string& xml); Burada referansı geçersiniz, temel olarak geçen işaretçiyle aynıdır, ancak derleyici bazı şeyler yapar ve geçersiz referansı geçemezsiniz (aslında geçersiz referansla durum oluşturmak mümkündür, ancak derleyiciyi kandırır).
  4. void foo(const std::string* xml); Burada ikinci ile aynı, sadece işaretçi değeri değiştirilemez.
  5. void foo(const std::string& xml); Burada üçüncü ile aynıdır, ancak nesne değeri değiştirilemez.

Daha fazla belirtmek istediğim, hangi tahsis yolunu seçerseniz seçin (ile newveya düzenli ) veri aktarmak için bu 5 yolu kullanabilirsiniz .


Bahsedilmesi gereken başka bir şey, nesneyi düzenli bir şekilde oluşturduğunuzda, belleği yığına ayırırsınız, ancak onu oluştururken newyığın ayırırsınız. Yığın tahsis etmek çok daha hızlıdır, ancak gerçekten büyük veri dizileri için küçüktür, bu nedenle büyük bir nesneye ihtiyacınız varsa yığın kullanmalısınız, çünkü yığın taşması alabilirsiniz, ancak genellikle bu sorun STL kapları kullanılarak çözülür ve hatırlanır std::stringayrıca konteyner, bazı çocuklar unuttu :)


5

Diyelim class Aki içerdiğiniz bir class Bfonksiyona sahip olduğunuzda class Bdışarıdan bir işlev çağırmak istediğinizde class Abu sınıfa bir işaretçi alacaksınız ve istediğiniz her şeyi yapabilirsiniz class B.class A

Ancak dinamik nesneye dikkat edin


5

İtiraz etmek için işaretçiler kullanmanın birçok faydası vardır -

  1. Verimlilik (daha önce işaret ettiğiniz gibi). Nesneleri işlevlere geçirmek, nesnenin yeni kopyalarını oluşturmak anlamına gelir.
  2. Üçüncü taraf kütüphanelerinden nesnelerle çalışma. Nesneniz bir üçüncü taraf koduna aitse ve yazarlar nesnelerini yalnızca işaretçiler aracılığıyla kullanmayı amaçlıyorsa (kopya oluşturucuları vb.) Bu nesnenin etrafından geçmenin tek yolu işaretçiler kullanmaktır. Değere göre geçmek sorunlara neden olabilir. (Derin kopyalama / sığ kopyalama sorunları).
  3. nesnenin bir kaynağı varsa ve sahipliğin diğer nesnelerle ilgilenmemesini istiyorsanız.

3

Bu uzunca tartışıldı, ancak Java'da her şey bir işaretçi. Yığın ve yığın ayırmaları arasında hiçbir ayrım yapmaz (tüm nesneler yığın üzerinde ayrılır), böylece işaretçiler kullandığınızı fark etmezsiniz. C ++ 'da, bellek gereksinimlerinize bağlı olarak ikisini karıştırabilirsiniz. Performans ve bellek kullanımı C ++ (duh) içinde daha belirleyicidir.


3
Object *myObject = new Object;

Bunu yaptığınızda, bellek sızıntısını önlemek için açıkça silinmesi gereken bir nesneye (öbek üzerinde) bir başvuru oluşturulur .

Object myObject;

Bunu yapmak , nesne (myObject) kapsam dışına çıktığında otomatik olarak silinecek otomatik türden (yığın üzerinde) bir nesne (myObject) oluşturur.


1

Bir işaretçi doğrudan bir nesnenin bellek konumuna başvurur. Java'nın böyle bir şeyi yoktur. Java, karma tablolar aracılığıyla nesnenin konumuna başvuran başvurulara sahiptir. Bu referanslarla Java'da işaretçi aritmetiği gibi bir şey yapamazsınız.

Sorunuzu cevaplamak için sadece sizin tercihiniz. Java benzeri sözdizimini kullanmayı tercih ederim.


Karma tablolar? Belki bazı JVM'lerde ama buna güvenmeyin.
Zan Lynx

Java ile gelen JVM ne olacak? Tabii ki doğrudan işaretçiler kullanan bir JVM veya işaretçi matematik yapan bir yöntem gibi düşünebilirsiniz HERHANGİ BİR uygulayabilirsiniz. Bu "insanlar soğuk algınlığından ölmez" demek ve "Belki çoğu insan buna güvenmiyor ama güvenmiyor!" Ha ha.
RioRicoRick

2
@RioRicoRick HotSpot, Java referanslarını yerel işaretçiler olarak uygular, bkz. Docs.oracle.com/javase/7/docs/technotes/guides/vm/… Görebildiğim kadarıyla JRockit de aynısını yapıyor. Her ikisi de OOP sıkıştırmasını destekler, ancak hiçbir zaman karma tabloları kullanmaz. Performans sonuçları muhtemelen felaket olacaktır. Ayrıca, "bu sadece sizin tercihinizdir", bu ikisinin eşdeğer davranışlar için sadece farklı sözdizimleri olduğu anlamına gelir.
Max Barraclough


0

İşaretçilerle ,

  • doğrudan bellekle konuşabilir.

  • işaretçileri işleyerek bir programın çok fazla bellek sızıntısını önleyebilir.


4
" C ++ 'ta, işaretçiler kullanarak, kendi programınız için özel bir çöp toplayıcı oluşturabilirsiniz " kulağa korkunç bir fikir gibi geliyor.
miktar

0

İşaretçileri kullanmanın bir nedeni, C işlevleriyle arabirim oluşturmaktır. Başka bir neden de hafızadan tasarruf etmektir; örneğin: çok fazla veri içeren ve işlemci yoğun bir kopya oluşturucuya sahip bir nesneyi bir işleve iletmek yerine, nesneye bir işaretçi geçirin, özellikle de döngüde iseniz bellek ve hızdan tasarruf edin, ancak C stili bir dizi kullanmıyorsanız referans bu durumda daha iyi olur.


0

Bellek kullanımının birinci sınıf olduğu alanlarda, işaretçiler kullanışlı olur. Örneğin, yinelemeli rutin kullanılarak binlerce düğümün oluşturulacağı bir minimax algoritması düşünün ve daha sonra bunları oyundaki bir sonraki en iyi hareketi değerlendirmek, kullanmak veya sıfırlamak (akıllı işaretçilerde olduğu gibi) yeteneğini bellek tüketimini önemli ölçüde azaltır. İşaretçi olmayan değişken, özyinelemeli çağrı bir değer döndürene kadar alan işgal etmeye devam eder.


0

İşaretçinin önemli bir kullanım durumunu içereceğim. Temel sınıfta bir nesne saklarken, ancak polimorfik olabilir.

Class Base1 {
};

Class Derived1 : public Base1 {
};


Class Base2 {
  Base *bObj;
  virtual void createMemerObects() = 0;
};

Class Derived2 {
  virtual void createMemerObects() {
    bObj = new Derived1();
  }
};

Bu durumda, bObj'yi doğrudan bir nesne olarak ilan edemezsiniz, işaretçiniz olmalıdır.


-5

"Zorunluluk icatların anasıdır." Belirtmek istediğim en önemli fark, kendi kodlama deneyimimin sonucudur. Bazen nesneleri fonksiyonlara geçirmeniz gerekir. Bu durumda, nesneniz çok büyük bir sınıftaysa, bir nesne olarak iletilmesi durumunu kopyalar (istemeyebilirsiniz .. VE BÜYÜK AŞIRI OLABİLİR), böylece nesne kopyalanır. 4 bayt boyutu (32 bit olduğu varsayılarak). Diğer nedenler yukarıda belirtilmiştir ...


14
referans ile geçmeyi tercih etmelisiniz
bolov

2
Değişken için olduğu gibi sabit referansla geçmeyi öneririm std::string test;, void func(const std::string &) {}ancak işlevin girişi değiştirmesi gerekmedikçe, bu durumda işaretçileri kullanmanızı öneririm (böylece kodu okuyan herkes fark eder &ve işlevi girdisini değiştirebilir)
Üst- Master

-7

Zaten çok sayıda mükemmel cevap var, ama size bir örnek vereyim:

Basit bir Item sınıfı var:

 class Item
    {
    public: 
      std::string name;
      int weight;
      int price;
    };

Bir demet tutmak için bir vektör yapıyorum.

std::vector<Item> inventory;

Bir milyon Item nesnesi oluşturuyorum ve onları vektör üzerine geri itiyorum. Vektörü ada göre sıralıyorum ve sonra belirli bir öğe adı için basit bir yinelemeli ikili arama yapıyorum. Programı test ediyorum ve yürütmenin tamamlanması 8 dakikadan fazla sürüyor. Sonra envanter vektörü şöyle değiştiriyorum:

std::vector<Item *> inventory;

... ve milyon adet Item nesnesimi yenisiyle oluştur. Kodumda yaptığım SADECE değişiklikler sonunda bellek temizleme için eklediğim bir döngü hariç Öğeler işaretçiler kullanmaktır. Bu program 40 saniyenin altında veya 10 kat hız artışından daha iyi çalışıyor. DÜZENLEME: Kod http://pastebin.com/DK24SPeW adresindedir Derleyici optimizasyonları ile, henüz test ettiğim makinede sadece 3.4 kat artış gösterir, ki bu hala dikkate değerdir.


2
Peki o zaman işaretçileri mi karşılaştırıyorsunuz yoksa hala gerçek nesneleri mi karşılaştırıyorsunuz? Başka bir dolaylılık seviyesinin performansı artırabileceğinden şüpheliyim. Lütfen kod verin! Daha sonra düzgün bir şekilde temizliyor musunuz?
stefan

1
@ stefan Nesnelerin verilerini (özellikle ad alanını) hem sıralama hem de arama için karşılaştırırım. Daha önce de belirttiğim gibi düzgün bir şekilde temizliyorum. hızlanma muhtemelen iki faktörden kaynaklanır: 1) std :: vector push_back () nesneleri kopyalar, bu nedenle işaretçi sürümünün her nesne için yalnızca tek bir işaretçiyi kopyalaması gerekir. Bu, yalnızca daha az veri kopyalandığında değil, vektör sınıfı bellek ayırıcısının daha az atıldığı için performans üzerinde birçok etkisi vardır.
Darren

2
İşte örneğiniz için neredeyse hiçbir farkı gösteren kod: sıralama. İşaretçi kodu yalnızca sıralama için işaretçi olmayan koddan% 6 daha hızlıdır, ancak genel olarak işaretçi olmayan koddan% 10 daha yavaştır. ideone.com/G0c7zw
stefan

3
Anahtar kelime: push_back. Elbette bu kopyalar. Sen olmalıydı emplace(başka bir yerde önbelleğe alınmasını onlara ihtiyacımız sürece) Nesnelerinizi oluştururken yerinde ing.
underscore_d

1
İşaretçilerin vektörleri neredeyse her zaman yanlıştır. Lütfen, uyarıları ve artıları ve eksileri ayrıntılı olarak açıklamadan tavsiye etmeyin. Bunu bir kötü kodlanmış karşı örnek sadece bir sonucudur ki, bir pro bulduk gibi görünüyor ve yanlış
Orbit Açıklık Yarışları
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.