raw, weak_ptr, unique_ptr, shared_ptr vb… Bunları akıllıca nasıl seçersiniz?


33

C ++ 'da çok fazla işaretçi var ancak C ++ programlamada (özellikle Qt Framework ile) 5 yıl kadar dürüst olmak gerekirse, sadece eski ham işaretçiyi kullanıyorum:

SomeKindOfObject *someKindOfObject = new SomeKindOfObject();

Bir sürü "akıllı" işaretçi olduğunu biliyorum:

// shared pointer:
shared_ptr<SomeKindofObject> Object;

// unique pointer:
unique_ptr<SomeKindofObject> Object;

// weak pointer:
weak_ptr<SomeKindofObject> Object;

Ancak onlarla ne yapılacağı ve ham işaretçilerle karşılaştırıldığında bana neler önerebilecekleri hakkında en ufak bir fikrim yok.

Örneğin, bu sınıf başlığına sahibim:

#ifndef LIBRARY
#define LIBRARY

class LIBRARY
{
public:
    // Permanent list that will be updated from time to time where
    // each items can be modified everywhere in the code:
    QList<ItemThatWillBeUsedEveryWhere*> listOfUselessThings; 
private:
    // Temporary reader that will read something to put in the list
    // and be quickly deleted:
    QSettings *_reader;
    // A dialog that will show something (just for the sake of example):
    QDialog *_dialog;
};

#endif 

Bu açıkça yorucu değil ama bu üç göstergenin her biri için onları "ham" bırakmak uygun mudur yoksa daha uygun bir şey mi kullanmalıyım?

İkinci kez, eğer bir işveren kodu okuyacaksa, ne tür işaretçiler kullanıp kullanmamam konusunda katı olacak mı?


Bu konu SO için çok uygun görünüyor. bu 2008’deydi . Ve işte ne zaman işaretçi kullanacağım? . Eminim daha iyi eşleşmeler bulabilirsin. Bunlar sadece ilk gördüğüm
15

Bunun sınır çizgisi imo çünkü bu sınıfların kavramsal anlamı / amacı, davranışlarının ve uygulamalarının teknik detayları ile ilgili. Kabul edilen cevap eskiye dayandığından, bu SO sorusunun "PSE versiyonu" olmaktan mutluyum.
Ixrec

Yanıtlar:


70

Bir "raw" işaretçisi yönetilmez. Yani, şu satır:

SomeKindOfObject *someKindOfObject = new SomeKindOfObject();

... eğer eşlik deleteetme uygun bir zamanda yapılmazsa, hafızaya sızacak.

auto_ptr

Bu davaları en aza indirmek std::auto_ptr<>için tanıtıldı. Bununla birlikte, 2011 standardından önceki C ++ sınırlamaları nedeniyle, auto_ptrbellek sızıntısı yapmak hala çok kolaydır . Ancak bunun gibi sınırlı durumlar için yeterlidir:

void func() {
    std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
    // do some work
    // will not leak if you do not copy sKOO_ptr.
}

En zayıf kullanım durumlarından biri konteynerlerdedir. Bunun nedeni, bir kopyanın bir kopyasının auto_ptr<>yapılması ve eski kopyanın dikkatlice sıfırlanmaması durumunda kabın işaretçiyi silip veri kaybedebilmesidir.

unique_ptr

Bunun yerine, C ++ 11 tanıtıldı std::unique_ptr<>:

void func2() {
    std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());

    func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}

Bu tür bir unique_ptr<>fonksiyon, fonksiyonlar arasında geçilse bile doğru bir şekilde temizlenecektir. Bunu, işaretçinin "sahipliğini" anlamsal olarak göstererek yapar - "sahip" temizler. Bu, kaplarda kullanım için idealdir:

std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();

Bunun aksine auto_ptr<>, unique_ptr<>burada iyi davranılmakta ve vectoryeniden boyutlandırıldığında, nesnelerden hiçbiri vectordestek deposunu kopyalarken yanlışlıkla silinmeyecektir .

shared_ptr ve weak_ptr

unique_ptr<>faydalı olduğundan emin olun, ancak kod tabanınızın iki bölümünün aynı nesneye başvurabilmesi ve işaretçiyi etrafından kopyalayabilmesini ancak yine de uygun temizliği garanti altına almasını istediğiniz durumlar vardır. Örneğin, bir ağaç kullanırken aşağıdaki gibi görünebilir std::shared_ptr<>:

template<class T>
struct Node {
    T value;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

Bu durumda, bir kök düğümünün birden fazla kopyasına bile tutabiliriz ve kök düğümün tüm kopyaları yok edildiğinde ağaç uygun şekilde temizlenir.

Bu, her biri shared_ptr<>yalnızca nesneye ilişkin işaretçiyi değil shared_ptr<>, aynı işaretçiyi ifade eden tüm nesnelerin referans sayısını da tuttuğu için çalışır . Yeni bir tane oluşturulduğunda, sayı artar. Biri yok edildiğinde sayım düşüyor. Sayım sıfıra ulaştığında, işaretçi deleted.

Bu da bir problem ortaya koyuyor: Çift bağlantılı yapılar dairesel referanslarla sonuçlanıyor. Ağacımıza bir parentişaretçi eklemek istediğimizi söyleyin Node:

template<class T>
struct Node {
    T value;
    std::shared_ptr<Node<T>> parent;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

Şimdi, eğer a kaldırırsak Node, buna döngüsel bir referans var. Asla deleted olmayacak çünkü referans sayısı asla sıfır olmayacak.

Bu sorunu çözmek için, bir kullanın std::weak_ptr<>:

template<class T>
struct Node {
    T value;
    std::weak_ptr<Node<T>> parent;
    std::shared_ptr<Node<T>> left;
    std::shared_ptr<Node<T>> right;
};

Şimdi, işler doğru şekilde çalışacak ve bir düğümün kaldırılması üst düğüme sıkışmış referanslar bırakmayacak. Ancak, ağacın yürümesini biraz daha karmaşık hale getirir:

std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();

Bu yolla, düğüme bir referansı kilitleyebilirsiniz ve üzerinde çalıştığınız için üzerinde çalışırken kaybolacağınıza dair makul bir garantiniz vardır shared_ptr<>.

make_shared ve make_unique

Şimdi, bazı küçük problemler var shared_ptr<>ve çözülmesi unique_ptr<>gerekenler var. Aşağıdaki iki satırın bir sorunu var:

foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());

Bir thrower()istisna atarsanız, her iki satırda da bellek sızıntısı olur. Ve daha da fazlası, shared_ptr<>bu işaret ve bu uzak nesneden başvuru sayısı tutar olabilir ) ikinci tahsisi anlamına gelir. Bu genellikle arzu edilmez.

C ++ 11 sağlar std::make_shared<>()ve C ++ 14 std::make_unique<>()bu sorunu çözmeyi sağlar :

foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());

Şimdi, her iki durumda da, thrower()bir istisna atsa bile , bir bellek sızıntısı olmayacak. Bir bonus olarak, size bir istisna güvenlik garantisi verirken, hem hızlı hem de birkaç byte'lık hafıza tasarrufu sağlayabilen, yönetilen nesnesiyle aynı hafıza alanındamake_shared<>() referans sayısını yaratma şansına sahip !

Qt hakkında notlar

Bununla birlikte, C ++ 11 öncesi derleyicileri desteklemesi gereken Qt'nin kendi çöp toplama modeline QObjectsahip olduğu belirtilmelidir : Birçoğunun , kullanıcının deletekendilerine ihtiyaç duymadan düzgün bir şekilde imha edileceği bir mekanizma vardır.

QObjectC ++ 11 işaretçiler tarafından yönetildiğinde nasıl davranacağını bilmiyorum , bu yüzden shared_ptr<QDialog>bunun iyi bir fikir olduğunu söyleyemem . Qt konusunda kesin olarak söyleyecek kadar tecrübem yok, ancak Qt5'in bu kullanım için ayarlanmış olduğuna inanıyorum .


1
@ Zilators: Lütfen Qt. Üç işaretleyicinin hepsinin de yönetilmesi gerekip gerekmediğine ilişkin sorunuzun cevabı, Qt nesnelerinin iyi davranıp davranmamasına bağlıdır.
greyfade,

2
"her ikisi de işaretçiyi tutmak için ayrı bir ayırma yapar"? Hayır, unique_ptr hiçbir zaman ekstra bir şey tahsis etmez, sadece shared_ptr bir reference-count + allocator-object tahsis etmelidir. "Her iki satırda da bellek kaçağı" hayır, sadece olabilir, hatta kötü davranışın garantisi de olmaz.
Deduplicator

1
@Deduplicator: İfadelerim açık olmamalıdır: ed nesnesinden shared_ptrayrı bir nesne - ayrı bir tahsisat - new. Farklı yerlerde varlar. make_sharedaynı şeyleri bir araya getirme yeteneğine sahiptir, bu da diğer şeylerin yanı sıra önbellek yerleşimini iyileştirir.
greyfade

2
@greyfade: Nononono. shared_ptrbir nesnedir. Ve bir nesneyi yönetmek için, bir (referans sayımları (zayıf + güçlü) + destroyer) -object ayırması gerekir. make_sharedBunu ve yönetilen nesneyi tek parça olarak tahsis etmeye izin verir. unique_ptrBunları kullanmaz, bu nedenle nesnenin daima akıllı işaretçiye ait olduğundan emin olmanın bir avantajı yoktur. Bir kenara, biri olabilir shared_ptrhangi sahibi altta yatan bir nesne ve bir temsil nullptrveya hangi kendi yapmaz ve sigara nullpointer temsil eder.
Deduplicator

1
Ona baktım ve ne olduğu hakkında genel bir karışıklık var gibi görünüyor shared_ptr: 1. Bazı nesnelerin sahipliğini paylaşıyor (zayıf ve güçlü bir referans sayımının yanı sıra bir silgiye sahip, dinamik olarak tahsis edilmiş bir dahili nesne ile temsil edilir) . 2. Bir işaretçi içerir. Bu iki bölüm bağımsızdır. make_uniqueve make_sharedher ikisi de tahsis edilen nesnenin akıllı bir işaretçiye güvenli bir şekilde yerleştirildiğinden emin olun. Ek olarak, make_sharedsahiplik nesnesini ve yönetilen işaretçiyi birlikte tahsis etmeye izin verir.
Deduplicator
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.