T'nin tam tanımını bilmek için std :: unique_ptr <T> gerekli mi?


248

Ben böyle görünüyor bir başlık bazı kod var:

#include <memory>

class Thing;

class MyClass
{
    std::unique_ptr< Thing > my_thing;
};

Bu üstbilgi Thingtür tanımı içermeyen bir cpp eklerseniz, bu VS2010-SP1 altında derlemez:

1> C: \ Program Dosyaları (x86) \ Microsoft Visual Studio 10.0 \ VC \ include \ memory (2067): hata C2027: tanımlanmamış 'Thing' türünün kullanımı

Değiştir std::unique_ptrtarafından std::shared_ptrve derler.

Yani, std::unique_ptrtam tanım gerektiren mevcut VS2010'un uygulaması olduğunu ve tamamen uygulamaya bağlı olduğunu tahmin ediyorum .

Yoksa öyle mi? Standart gereksinimlerinde, std::unique_ptruygulamanın yalnızca ileri bir beyan ile çalışmasını imkansız kılan bir şey var mı ? Sadece bir işaretçi tutması gerektiği için garip geliyor Thing, değil mi?


20
C ++ 0x akıllı işaretçilerle tam bir türe gerek duyduğunuzda ve buna gerek olmadığında en iyi açıklama Howard Hinnant'ın "Eksik türleri ve shared_ptr/ unique_ptr" Sonundaki tablo sorunuzu cevaplamalıdır.
James McNellis

17
İşaretçi James için teşekkürler. O masayı nereye koyduğumu unutmuştum! :-)
Howard Hinnant


5
@JamesMcNellis Howard Hinnant'ın web sitesine bağlantı kesildi. İşte web.archive.org sürümü . Her halükarda, aynı içerikle aşağıda mükemmel bir şekilde cevapladı :-)
Ela782

Başka bir iyi açıklama Scott Meyers'in Etkili modern C ++ 'sında 22. Maddede verilmiştir.
Fred Schoen

Yanıtlar:


328

Buradan benimsendi .

C ++ standart kitaplığındaki şablonların çoğu, tam türlerle başlatılmasını gerektirir. Ancak shared_ptrve unique_ptrvardır kısmi istisnalar. Üyelerinin tamamı olmasa da bazıları eksik tiplerle somutlaştırılabilir. Bunun motivasyonu, akıllı işaretçiler kullanarak ve tanımlanmamış davranışları riske atmadan pimpl gibi deyimleri desteklemektir .

Tanımsız bir tipe sahip olduğunuzda ve bunu çağırdığınızda tanımlanmamış davranış oluşabilir delete:

class A;
A* a = ...;
delete a;

Yukarıdaki yasal kod. Derlenecek. Derleyiciniz yukarıdaki gibi yukarıdaki kod için bir uyarı verebilir veya vermeyebilir. Yürütüldüğünde, muhtemelen kötü şeyler olacaktır. Çok şanslıysanız, programınız çökecektir. Ancak daha olası bir sonuç, programınızın sessiz bir şekilde bellek ~A()çağrısında bulunmayacağıdır.

auto_ptr<A>Yukarıdaki örnekte kullanmak yardımcı olmuyor. Ham bir işaretçi kullansanız bile tanımlanmamış davranışı elde edersiniz.

Bununla birlikte, bazı yerlerde eksik sınıfların kullanılması çok yararlıdır! Burası shared_ptrve unique_ptryardım. Bu akıllı işaretçilerin birinin kullanılması, tam bir türe sahip olmanın gerekli olduğu durumlar dışında, eksik bir türden kurtulmanıza izin verecektir. Ve en önemlisi, tam bir türe sahip olmak gerektiğinde, akıllı işaretçiyi o noktada eksik bir türle kullanmaya çalışırsanız derleme zamanı hatası alırsınız.

Artık tanımlanmamış davranış yok:

Kodunuz derlenirse, ihtiyacınız olan her yerde tam bir tür kullandınız.

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

shared_ptrve unique_ptrfarklı yerlerde tam bir tür gerektirir. Bunun nedenleri, dinamik bir deleter ile statik bir deleter arasındaki ilginin belirsiz olmasıdır. Kesin nedenler önemli değil. Aslında, çoğu kodda tam bir türün tam olarak nerede gerekli olduğunu bilmeniz gerçekten önemli değildir. Sadece kodlayın ve yanlış yaparsanız derleyici size söyleyecektir.

Bununla birlikte, size yardımcı olması durumunda, burada birkaç üyeyi shared_ptrve unique_ptreksiksizlik gerekliliklerini belgeleyen bir tablo bulunmaktadır . Üye tam bir tür gerektiriyorsa, girdide "C" bulunur, aksi takdirde tablo girdisi "I" ile doldurulur.

Complete type requirements for unique_ptr and shared_ptr

                            unique_ptr       shared_ptr
+------------------------+---------------+---------------+
|          P()           |      I        |      I        |
|  default constructor   |               |               |
+------------------------+---------------+---------------+
|      P(const P&)       |     N/A       |      I        |
|    copy constructor    |               |               |
+------------------------+---------------+---------------+
|         P(P&&)         |      I        |      I        |
|    move constructor    |               |               |
+------------------------+---------------+---------------+
|         ~P()           |      C        |      I        |
|       destructor       |               |               |
+------------------------+---------------+---------------+
|         P(A*)          |      I        |      C        |
+------------------------+---------------+---------------+
|  operator=(const P&)   |     N/A       |      I        |
|    copy assignment     |               |               |
+------------------------+---------------+---------------+
|    operator=(P&&)      |      C        |      I        |
|    move assignment     |               |               |
+------------------------+---------------+---------------+
|        reset()         |      C        |      I        |
+------------------------+---------------+---------------+
|       reset(A*)        |      C        |      C        |
+------------------------+---------------+---------------+

İşaretçi dönüşümü gerektiren işlemler hem unique_ptrve için tam türler gerektirir shared_ptr.

unique_ptr<A>{A*}Yapıcı bir kurtulabiliriz eksik Asadece derleyici bir çağrı kurmak için gerekli değilse ~unique_ptr<A>(). Örneğin unique_ptr, öbeğe koyarsanız, eksik bir şeyden kurtulabilirsiniz A. Bu noktada fazla detay bulunabilir BarryTheHatchet en cevabı burada .


3
Mükemmel cevap. Yapabilirsem +5 olurdu. Eminim akıllı işaretçilerden tam olarak yararlanmaya çalışacağım bir sonraki projemde bundan bahsedeceğim.
12th matthias

4
tablonun ne anlama geldiğini açıklayabilirsek sanırım daha fazla insana yardımcı olacaktır
Ghita

8
Bir not daha: Bir sınıf kurucusu üyelerinin yıkıcılarına atıfta bulunacaktır (bir istisna atıldığında, bu yıkıcıların çağrılması gerekir). Bu nedenle unique_ptr imha edicisinin tam bir türe ihtiyacı olsa da, bir sınıfta kullanıcı tanımlı bir imha ediciye sahip olmak yeterli değildir - aynı zamanda bir kurucuya da ihtiyaç duyar.
Johannes Schaub - litb

7
@Mehrdad: Bu karar benim zamanımdan önceki C ++ 98 için verildi. Bununla birlikte, kararın uygulanabilirlik ve şartnamenin zorluğu ile ilgili bir endişeden kaynaklandığına inanıyorum (yani bir kabın tam olarak hangi bölümlerinin tam tip gerektirmediği veya gerektirmediği). Bugün bile, C ++ 98'den bu yana 15 yıllık deneyime sahip olmak, hem bu alandaki konteyner spesifikasyonunu gevşetmek hem de önemli uygulama tekniklerini veya optimizasyonlarını yasadışı bırakmamanızı sağlamak önemsiz bir görev olacaktır. Ben düşünüyorum da yapılabilir. Çok iş olacağını biliyorum . Denemeyi yapan bir kişinin farkındayım.
Howard Hinnant

9
Onlar tanımlamak için değil, yukarıdaki yorumlardan bariz bu sorunu yaşıyor herkes için Çünkü unique_ptrsadece bir sınıfın üyesi değişkeni olarak açıkça (başlık dosyasında) sınıf bildiriminde içinde yıkıcı (ve yapıcı) beyan ve devam tanımlamak onları derleyicinin yapıcıyı veya yıkıcıyı başlık dosyasında otomatik olarak satır içine almasını (hatayı tetikleyen) önlemek için kaynak dosyasında (ve üstbilgiyi kaynak dosyada sivri uçlu sınıfın tam bildirimi ile birlikte) koyun. stackoverflow.com/a/13414884/368896 da bunu hatırlatmamda yardımcı oluyor.
Dan Nissenbaum

42

Derleyici, MyClass için varsayılan yıkıcıyı oluşturmak için Thing tanımına ihtiyaç duyar. Yıkıcıyı açıkça bildirir ve (boş) uygulamasını CPP dosyasına taşırsanız, kod derlenmelidir.


5
Bunun varsayılan bir işlevi kullanmak için mükemmel bir fırsat olduğunu düşünüyorum. MyClass::~MyClass() = default;Uygulama dosyasında, destuctor gövdesinin kasıtlı olarak boş bırakılmak yerine silindiğini varsayan bir kişi tarafından yanlışlıkla daha sonra kaldırılması daha az olası görünüyor.
Dennis Zickefoose

@Dennis Zickefoose: Ne yazık ki OP VC ++ kullanıyor ve VC ++ henüz defaulted ve deleted sınıfı üyelerini desteklemiyor.
ildjarn

6
Kapının .cpp dosyasına nasıl taşınacağı hakkında +1. Ayrıca MyClass::~MyClass() = defaultClang üzerinde uygulama dosyasına taşımaz gibi görünüyor . (Henüz?)
Eonil

Ayrıca, yapıcı uygulamasını en azından VS 2017'de CPP dosyasına taşımanız gerekir. Örneğin bu cevaba bakın: stackoverflow.com/a/27624369/5124002
jciloa

15

Bu uygulamaya bağlı değildir. Çalışmasının nedeni shared_ptr, çalışma zamanında çağrılacak doğru yıkıcıyı belirlemesidir - tür imzasının bir parçası değildir. Bununla birlikte, unique_ptr's yıkıcı olan türünün bir parçası ve derleme zamanında bilinmelidir.


8

Görünüşe göre mevcut cevaplar varsayılan kurucu (veya yıkıcı) neden sorun değil tam olarak çivileme değil ama cpp bildirilen boş cevaplar.

İşte olanlar:

Dış sınıf (yani Sınıfım) yapıcı veya yıkıcıya sahip değilse, derleyici varsayılanları oluşturur. Bu sorun derleyicinin varsayılan boş yapıcı / destructor .hpp dosyasında ekler olmasıdır. Bu, varsayılan oluşturucu / yıkıcı kodunun, kitaplığınızın ikili dosyalarıyla değil, ana bilgisayar yürütülebilir dosyasının ikili dosyasıyla birlikte derlendiği anlamına gelir. Ancak bu tanımlar gerçekten kısmi sınıfları oluşturamaz. Bu yüzden linker kütüphanenizin ikili dosyasına girdiğinde ve yapıcı / yıkıcı almaya çalıştığında, herhangi bir şey bulamaz ve hata alırsınız. Yapıcı / yıkıcı kodu .cpp'nizdeyse, kütüphane ikili dosyasında bağlantı için kullanılabilir kod bulunur.

Bu unique_ptr veya shared_ptr kullanımı ile ilgisi yoktur ve diğer cevaplar unique_ptr uygulaması için eski VC ++ 'da kafa karıştırıcı bir hata gibi görünüyor (VC ++ 2015 makinemde iyi çalışıyor).

Hikayenin ahlakı, başlığınızın herhangi bir kurucu / yıkıcı tanımından uzak kalması gerektiğidir. Yalnızca beyanlarını içerebilir. Örneğin, ~MyClass()=default;hpp'de çalışmaz. Derleyicinin varsayılan yapıcı veya yıkıcı eklemesine izin verirseniz, bir bağlayıcı hatası alırsınız.

Diğer bir yan not: cpp dosyasında yapıcı ve yıkıcı sonra bile hala bu hatayı alıyorsanız büyük olasılıkla kitaplığınızın düzgün derlenmiş değil olmasıdır. Örneğin, bir kez proje türünü Konsol'dan VC ++ 'da Kütüphane'ye değiştirdim ve VC ++ _LIB önişlemci sembolü eklemediğinden ve aynı hata mesajını ürettiğinden bu hatayı aldım.


Teşekkür ederim! Bu, inanılmaz derecede belirsiz bir C ++ tuhaflığının çok kısa bir açıklamasıydı. Beni çok beladan kurtardı.
JPNotADragon

5

Sadece bütünlük için:

Başlık: Ah

class B; // forward declaration

class A
{
    std::unique_ptr<B> ptr_;  // ok!  
public:
    A();
    ~A();
    // ...
};

Kaynak A.cpp:

class B {  ...  }; // class definition

A::A() { ... }
A::~A() { ... }

B sınıfının tanımı, yapıcı, yıkıcı ve B'yi kesin olarak silebilecek herhangi bir şey tarafından görülmelidir (Yapıcı yukarıdaki listede görünmese de, VS2017'de yapıcı bile B tanımına ihtiyaç duyar ve bu dikkate alırken mantıklıdır. yapıcıda bir istisna olması durumunda unique_ptr öğesinin yeniden imha edildiğini unutmayın.)


1

Şey'in tam tanımı, şablon örnekleme noktasında gereklidir. Bu pimpl deyiminin derlenmesinin tam sebebidir.

Bu mümkün değildi, insanlar gibi sorular sormak olmaz bu .



-7

Benim açımdan,

QList<QSharedPointer<ControllerBase>> controllers;

Sadece başlığı ekleyin ...

#include <QSharedPointer>

Cevap ilgili değil ve soruyla ilgili değil.
Mikus
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.