Sınıf üyeleri için akıllı işaretçiler kullanma


159

C ++ 11 sınıf üyeleri olarak akıllı işaretçi kullanımını anlamakta sorun yaşıyorum. Akıllı işaretçiler hakkında çok şey okudum unique_ptrve genel olarak nasıl ve shared_ptr/ nasıl weak_ptrçalıştığını anladığımı düşünüyorum . Anlamadığım şey gerçek kullanımdır. Herkesin unique_ptrneredeyse her zaman gitmek için bir yol olarak kullanılmasını önerdiği görülüyor . Ama böyle bir şeyi nasıl uygularım:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

İşaretçileri akıllı işaretçilerle değiştirmek istediğimi varsayalım. Bir unique_ptrnedeniyle değil iş olur getDevice(), doğru? Yani o zaman shared_ptrve weak_ptr? Kullanmanın yolu yok unique_ptrmu? Bana shared_ptrgerçekten küçük bir kapsamda bir işaretçi kullanmadığı sürece çoğu durumda daha mantıklı gibi görünüyor ?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

Gidilecek yol bu mu? Çok teşekkürler!


4
Ömür boyu, sahiplik ve olası null'lar konusunda gerçekten net olmaya yardımcı olur. Örneğin device, yapıcısına geçtikten sonra, settingshala arama kapsamında veya yalnızca üzerinden başvuruda bulunabilmek ister settingsmisiniz? İkincisi, unique_ptrfaydalıdır. Ayrıca, dönüş değeri nerede bir senaryo var getDevice()olduğunu null. Değilse, sadece bir referans gönderin.
Keith

2
Evet, shared_ptr8/10 vakalarında a doğrudur. Diğer 2/10 ise unique_ptrve arasında bölünür weak_ptr. Ayrıca, weak_ptrgenellikle dairesel referansları kırmak için kullanılır; Kullanımınızın doğru kabul edileceğinden emin değilim.
Collin Dauphinee

2
Her şeyden önce, deviceveri üyesi için hangi sahipliği istersiniz ? Önce buna karar vermelisin.
juanchopanza

1
Tamam, anlıyorum ki, arayan olarak unique_ptrbunun yerine kullanacağımı ve yapıcıyı çağırırken sahipliğini bırakabileceğimi anlıyorum , eğer şimdi buna ihtiyacım olmayacağını biliyorum. Ama Settingssınıfın tasarımcısı olarak arayanın da bir referans tutmak isteyip istemediğini bilmiyorum. Belki cihaz birçok yerde kullanılacaktır. Tamam, belki de bu tam olarak sizin açınızdan. Bu durumda, ben tek sahibi olmaz ve o zaman paylaşılan_ptr kullanmak istiyorum, sanırım. Ve: akıllı noktalar işaretçilerin yerini alır, ancak referansları değil, değil mi?
michaelk

bu-> cihaz = cihaz; Ayrıca başlatma listelerini kullanın.
Nils

Yanıtlar:


202

Bir unique_ptrnedeniyle değil iş olur getDevice(), doğru?

Hayır, ille de değil. Burada önemli olan, nesneniz için uygun sahiplik politikasını belirlemek Device, yani (akıllı) işaretçinizin işaret ettiği nesnenin sahibi kim olacaktır?

SadeceSettings nesnenin örneği mi olacak ? Will nesne otomatik olarak imha edilmesi gereken nesne yok edildi, tam o nesneyi uzun yaşamamalı?DeviceSettings

İlk durumda, std::unique_ptrihtiyacınız olan şeydir, çünkü Settingssivri nesnenin tek (benzersiz) sahibini ve yıkımından sorumlu tek nesneyi yapar.

Bu varsayım altında, getDevice()basit bir gözlem işaretçisi döndürmelidir (gözlem işaretçileri sivri uçlu nesneyi canlı tutmayan işaretçilerdir). En basit gözlemci işaretçisi ham bir işaretleyicidir:

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

[ NOT 1: Herkes ham işaretçilerin kötü, güvensiz ve tehlikeli olduğunu söylerken neden burada ham işaretçiler kullandığımı merak ediyor olabilirsiniz. Aslında, bu değerli bir uyarıdır, ancak doğru bağlama koymak önemlidir: ham bellek göstergeleri manuel bellek yönetimi gerçekleştirmek için kullanıldığında kötüdür , yani nesnelerin newve aracılığıyla tahsis edilmesi ve yerleştirilmesi delete. Sadece referans anlambilimine ulaşmak ve sahip olmama, gözlemcileri gözlemlemek için bir araç olarak kullanıldığında, ham işaretçilerde kendiliğinden tehlikeli bir şey yoktur, belki de sarkan bir işaretçiyi terk etmemeye dikkat etmelidir. - SON NOT 1 ]

[ NOT 2: Yorumlarda ortaya çıktığı gibi, mülkiyetin benzersiz olduğu ve sahip olunan nesnenin her zaman mevcut olduğu garanti edilen (yani dahili veri üyesinin deviceasla olmayacağı nullptr) bu özel durumda , işlev getDevice()olabilir (ve belki de olmalıdır) işaretçi yerine başvuru döndürür. Bu doğru olsa da, burada ham bir işaretçi döndürmeye karar verdim çünkü devicebunun nullptr, nerede olabileceği durumuna genelleştirilebileceği kısa bir cevap olması ve ham işaretçilerin bunları kullanmadığı sürece iyi olduğunu göstermek istedim . elle bellek yönetimi. - SON NOT 2 ]


Senin eğer durum, tabii ki, kökten farklı Settingsnesne olmalıdır değil cihazın münhasır sahibi. Örneğin, Settingsnesnenin imhası, sivri Devicenesnenin de imha edilmesini ima etmiyorsa böyle olabilir .

Bu sadece sizin programınızın tasarımcısı olarak anlatabileceğiniz bir şeydir; verdiğiniz örnekten, durumun böyle olup olmadığını söylemek benim için zor.

Anlamanıza yardımcı olmak için, kendinize , pasif gözlemciler olmak yerine Settings, Devicenesneyi bir işaretçi tuttuğu sürece hayatta tutma hakkına sahip başka nesneler olup olmadığını sorabilirsiniz . Eğer durum gerçekten de ise, o zaman bir ihtiyaç müşterek mülkiyet politikası nedir, std::shared_ptrteklifler:

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

Bildirim, weak_ptrbir olduğunu gözlemleyerek işaretçi değil bir sahibi işaretçi - sivri nesneye diğer tüm sahip olma işaretçileri kapsam dışında giderseniz başka bir deyişle, hayatta sivri nesne tutmaz.

weak_ptrDüzenli bir ham işaretçinin avantajı weak_ptr, sarkık olup olmadığını (yani geçerli bir nesneyi gösterip göstermediğini veya orijinal olarak işaret edilen nesnenin yok edilip edilmediğini) güvenli bir şekilde anlayabilmenizdir . Bu expired(), weak_ptrnesne üzerindeki üye işlevini çağırarak yapılabilir .


4
@LKK: Evet, doğru. A weak_ptrher zaman ham gözlem işaretlerine bir alternatiftir. Bir anlamda daha güvenlidir, çünkü kayıttan çıkarmadan önce sarkıp söndüğünü kontrol edebilirsiniz, ancak aynı zamanda bir miktar ek yük ile birlikte gelir. Eğer sarkan bir işaretçiyi terk etmeyeceğinizi kolayca garanti ediyorsanız, ham işaretçileri gözlemlemede iyi olmalısınız
Andy Prowl

6
İlk durumda getDevice(), bir referansın geri dönmesine izin vermek muhtemelen daha iyi olurdu , değil mi? Bu yüzden arayanın kontrol etmesi gerekmez nullptr.
vobject

5
@chico: Ne demek istediğinden emin değilim. auto myDevice = settings.getDevice()türde yeni bir örneğini oluşturur Devicedenilen myDeviceve o referans olarak başvurulan birinden kopyalayıp inşa getDevice()getiri. İsterseniz myDevicebir başvuru olması, yapmanız gereken auto& myDevice = settings.getDevice(). Yani bir şeyi kaçırmadıkça, kullanmadan yaşadığımız duruma geri döndük auto.
Andy Prowl

2
@Purrformance: Nesnenin sahipliğini vermek istemediğiniz için - unique_ptristemciye değiştirilebilen bir şey teslim etmek, istemcinin kendisinden hareket etme olasılığını açar, böylece sahiplik edinir ve sizi boş (benzersiz) bir işaretçi ile bırakır.
Andy Prowl

7
@Purrformance: Bu bir müşterinin hareket etmesini engelleyecek olsa da (müşteri seye meraklı bir bilim adamı const_castdeğilse) kişisel olarak yapmazdım. Bir uygulama detayını, yani sahipliğin benzersiz ve a unique_ptr. İşleri şu şekilde görüyorum: Sahipliği geçmek / iade etmek istiyorsanız / akıllı bir işaretçiyi ( unique_ptrveya shared_ptrsahiplik türüne bağlı olarak) geçmek / iade etmek istiyorsanız . Sahipliği iletmek / iade etmek istemiyorsanız / buna gerek constduymuyorsanız, çoğunlukla argümanın boş olup olmamasına bağlı olarak (düzgün şekilde nitelendirilmiş) bir işaretçi veya referans kullanın .
Andy Prowl

0
class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptryalnızca referans döngüleri için kullanılır. Bağımlılık grafiği, çevrimsizleştirilmiş grafik olmalıdır. Paylaşılan işaretçilerde 2 referans sayımı vardır: shared_ptrs için 1 ve tüm işaretçiler için ( shared_ptrve weak_ptr) 1. Tümü shared_ptrkaldırıldığında işaretçi silinir. İşaretçi gelen gerektiğinde weak_ptr, lockeğer varsa, işaretçi almak için kullanılmalıdır.


Cevabınızı doğru bir şekilde anlarsam, akıllı işaretçiler ham işaretçilerin yerini alır, ancak mutlaka referanslar değil mi?
michaelk

A'da aslında iki referans sayımı var shared_ptrmı? Nedenini açıklayabilir misiniz? Anladığım kadarıyla, weak_ptrsayılmak zorunda değil çünkü shared_ptrnesne üzerinde çalışırken sadece yeni bir nesne yaratır (alttaki nesne hala mevcutsa).
Björn Pollex

@ BjörnPollex: Sizin için kısa bir örnek oluşturdum: link . Ben sadece kopya kurucuları ve lock. boost sürümü ayrıca referans sayımında iş parçacığı için güvenlidir ( deleteyalnızca bir kez çağrılır).
Mart'ta Naszta

@Naszta: Örneğiniz, bunu iki referans sayısı kullanarak uygulamanın mümkün olduğunu gösteriyor , ancak cevabınız bunun gerekli olduğunu gösteriyor , ki buna inanmıyorum. Cevabınızda bunu açıklığa kavuşturabilir misiniz?
Björn Pollex

1
@ BjörnPollex, weak_ptr::lock()nesnenin süresinin dolup dolmadığını söylemek için, nesneye ilk referans sayısını ve işaretçiyi içeren "kontrol bloğunu" kontrol etmelidir, bu nedenle weak_ptrhala kullanımda olan herhangi bir nesne varken kontrol bloğu imha edilmemelidir , böylece weak_ptrikinci referans sayısının yaptığı nesne sayısı izlenmelidir. Birinci ref sayımı sıfıra düştüğünde nesne imha edilir, ikinci ref sayımı sıfıra düştüğünde kontrol bloğu imha edilir.
Jonathan Wakely
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.