C ++ kitaplıkları ve çerçeveleri neden asla akıllı işaretçiler kullanmıyor?


156

Birkaç makalede, ham işaretçilerin neredeyse hiç kullanılmaması gerektiğini okudum. Bunun yerine, ister kapsamlandırılmış ister paylaşılan işaretçiler olsun, daima akıllı işaretçilerin içine sarılmalıdır.

Ancak, Qt, wxWidgets ve Boost gibi kütüphanelerin, sanki hiç kullanmıyormuş gibi, akıllı işaretçiler asla geri dönmediğini veya beklemediğini fark ettim. Bunun yerine geri dönerler veya ham işaretçiler beklerler. Bunun için bir sebep var mı? Herkese açık bir API yazarken akıllı işaretçilerden uzak durmalı mıyım ve neden?

Sadece birçok büyük proje onlardan kaçındığı zaman akıllı işaretçilerin neden önerildiğini merak ediyorum.


22
Yeni adlandırdığınız kütüphanelerin tümü yıllar önce başlatıldı. Akıllı işaretçiler yalnızca C ++ 11'de gerçekten standart hale geldi.
chrisaycock

22
akıllı işaretçiler, örneğin gömülü / gerçek zamanlı sistemlerde (kritik sayım gibi) bir ek yüke (referans sayma vb.) sahiptir. IMHO - akıllı işaretçiler tembel programcılar içindir. Ayrıca çok sayıda API en düşük ortak payda için geçerlidir. Yazarken alevlerin ayaklarımın etrafında yaladığını hissediyorum!
Ed Heal

93
@EdHeal: Alevlerin ayaklarınızın etrafında yaladığını hissetmenizin nedeni, her açıdan tamamen yanılıyorsunuz. Örneğin, içinde hangi ek yük var unique_ptr? Hiçbiri. Qt / WxWidgets katıştırılmış veya gerçek zamanlı sistemlere yönelik mi? Hayır, en çok bir masaüstünde Windows / Mac / Unix için tasarlanmıştır. Akıllı işaretçiler, düzeltmek isteyen programcılar içindir.
Köpek yavrusu

24
Gerçekten, cep telefonları Java çalıştırıyor.
R. Martinho Fernandes

12
Akıllı işaretçiler sadece C ++ 11'de gerçekten standart mı? Ne??? Bunlar 20 yıldan fazla bir süredir kullanılmaktadır.
Kaz

Yanıtlar:


124

Standart akıllı işaretçilerin ortaya çıkmasından önce birçok kütüphanenin yazılmasının yanı sıra, en büyük neden muhtemelen standart bir C ++ Uygulama İkili Arabirimi'nin (ABI) olmamasıdır.

Yalnızca başlık içeren bir kitaplık yazıyorsanız, akıllı işaretçileri ve standart kapları kalbinizin içeriğine aktarabilirsiniz. Kaynakları derleme zamanında kitaplığınızda kullanılabilir, bu nedenle uygulamalarının değil, yalnızca arabirimlerinin kararlılığına güvenirsiniz.

Ama nedeniyle standart ABİ eksikliği, genellikle edemez modül sınırlarının ötesinde güvenle bu nesneleri geçmektedir. Bir GCC shared_ptrmuhtemelen shared_ptrbir Intel'den farklı olabilecek bir MSVC'den farklıdır shared_ptr. Aynı derleyicide bile , bu sınıfların sürümler arasında ikili uyumlu olması garanti edilmez.

Sonuç olarak, kitaplığınızın önceden oluşturulmuş bir sürümünü dağıtmak istiyorsanız , güvenebileceğiniz standart bir ABI'ye ihtiyacınız vardır. C'nin bir tane yok, ancak derleyici satıcıları belirli bir platform için C kütüphaneleri arasında birlikte çalışabilirlik konusunda çok iyi - fiili standartlar var.

Durum C ++ için iyi değil. Bireysel derleyiciler kendi ikili dosyaları arasında birlikte çalışabilir, bu nedenle genellikle GCC ve MSVC olmak üzere desteklenen her derleyici için bir sürüm dağıtma seçeneğiniz vardır. Ancak bunun ışığında, çoğu kütüphane sadece bir C arayüzü dışa aktarır ve bu ham işaretçiler anlamına gelir.

Bununla birlikte, kütüphane dışı kod genellikle ham işaretçiler yerine akıllı işaretçileri tercih etmelidir.


17
Sana katılıyorum, hatta bir std :: string geçen bir acı olabilir, Bu C ++ hakkında "kütüphaneler için harika bir dil" olarak çok şey söylüyor.
Ha11owed

8
Alt satır daha çok benzer: Eğer bir ön-yapım sürümünü dağıtmak istiyorsanız, bunu desteklemek istediğiniz her derleyici için yapmanız gerekir.
josefx

6
@josefx: Evet bu üzücü ama gerçek, tek alternatif COM veya ham C arayüzü. Keşke C ++ comity bu tür sorunlar için kötüleşmeye başlayacaktı. Demek istediğim, C ++ 2 yıl önceki yeni bir dil gibi değil.
Robot Mess

3
İndirdim çünkü bu yanlış. ABI sorunları çoğu durumda yönetilenden daha fazladır. Kullanıcı dostu olmakla birlikte, ABI da aşılamaz.
Köpek yavrusu

4
@NathanAdams: Bu yazılım şüphesiz etkileyici ve kullanışlıdır. Ancak daha derin sorunların belirtilerini tedavi eder: C ++ yaşam boyu ve mülkiyet semantiği yoksul ve var olmayanlar arasında bir yerdedir. Dil onlara izin vermezse bu yığın hataları ortaya çıkmazdı. Elbette, akıllı işaretçiler her derde deva değildir - en başta C ++ kullanarak oluşan kayıpların bir kısmını telafi etme girişimidir.
Jon Purdy

40

Bunun birçok nedeni olabilir. Bunlardan birkaçını listelemek için:

  1. Akıllı işaretçiler son zamanlarda standardın bir parçası haline geldi. O zamana kadar diğer kütüphanelerin bir parçasıydılar
  2. Birincil kullanımları bellek sızıntılarından kaçınmaktır; birçok kütüphanenin kendi bellek yönetimi yoktur; Genellikle yardımcı programlar ve API'lar sağlarlar
  3. Bunlar aslında işaretçiler değil, nesneler oldukları için sarıcı olarak uygulanırlar. Ham işaretçilerle karşılaştırıldığında ek zaman / alan maliyeti olan; Kütüphanelerin kullanıcıları böyle bir ek yüke sahip olmak istemeyebilirler

Düzenleme : Akıllı işaretçiler kullanmak tamamen geliştiricinin seçimidir. Çeşitli faktörlere bağlıdır.

  1. Performans açısından kritik öneme sahip sistemlerde, ek yük oluşturan akıllı işaretçiler kullanmak istemeyebilirsiniz

  2. Geriye dönük uyumluluğa ihtiyaç duyan proje, C ++ 11'e özgü özelliklere sahip akıllı işaretçileri kullanmak istemeyebilirsiniz

Edit2 Aşağıdaki pasaj nedeniyle 24 saat içinde birkaç downvotes dizisi var. Aşağıda bir yanıt değil, yalnızca bir eklenti önerisi olmasına rağmen cevabın neden reddedildiğini anlayamıyorum.
Ancak, C ++ her zaman seçeneklerin açık olmasını sağlar. :) Örneğin

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

Ve kodunuzda şu şekilde kullanın:

Pointer<int>::type p;

Akıllı bir işaretçinin ve ham bir işaretçinin farklı olduğunu söyleyenler için buna katılıyorum. Yukarıdaki kod, sadece a ile değiştirilebilir bir kod yazabileceği bir fikirdi#define , bu bir zorlama değil ;

Örneğin, T*açıkça silinmesi gerekir , ancak akıllı bir işaretçi silinmez. Bununla Destroy()başa çıkmak için bir şablon olabilir .

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

ve şu şekilde kullanın:

Destroy(p);

Aynı şekilde, ham bir işaretçi için doğrudan kopyalayabiliriz ve akıllı işaretçi için özel bir işlem kullanabiliriz.

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

Nerede Assign():

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}

14
Açık 3. Bazı akıllı işaretçilerin ek zaman / alan maliyetleri vardır, diğerleri de std::auto_ptruzun süredir standardın bir parçası olmuştur (ve not edin, std::auto_ptrnesneler yaratan işlevler için dönüş türü olarak beğenirim) neredeyse her yerde işe yaramaz). C ++ 11'de std::unique_ptrdüz bir işaretçi üzerinden ek maliyet yoktur.
David Rodríguez - dribeas

4
Aynen ... görünümü güzel bir simetri vardır unique_ptrve ortadan kalkması auto_ptr, kod eski kullanabilirsiniz C ++ 11 hedeflerken C ++ 03, daha sonra kullanması gereken hedeflemesi kodu. Akıllı işaretçiler değil shared_ptr , birçok standart var ve standart yok, standart olarak reddedilen teklifler de dahil olmak üzeremanaged_ptr
David Rodríguez - dribeas

2
@iammilind, bunlar ilginç noktalar, ama komik olan şey, görünüşe göre birçok kişinin önereceği gibi, akıllı işaretçiler kullanırsak, büyük kütüphanelerle uyumsuz kod oluşturmamızdır. Tabii ki, akıllı işaretçileri gerektiği gibi sarabilir / paketini açabiliriz, ancak çok fazla güçlük gibi görünür ve tutarsız kod yaratır (bazen akıllı işaretçilerle ilgileniriz, bazen değil).
laurent

7
Akıllı işaretçilerin "ek zaman / alan maliyeti" ifadesi biraz yanıltıcıdır; tüm akıllı işaretçiler unique_ptrçalışma zamanı maliyeti hariç , ancak unique_ptraçık ara en yaygın kullanılan işaretçilerdir . Çünkü sağlamak kod örneği ayrıca, yanıltıcıdır unique_ptrve T*tamamen farklı kavramlardır. Her ikisine de atıfta bulunmanız type, birbirleri için değiştirilebilecekleri izlenimini veriyor.
void-pointer

12
Böyle tanımlayamazsınız, bu türler hiçbir şekilde eşdeğer değildir. Bunun gibi typedefs yazmak sorun istiyor.
Alex B

35

Akıllı işaretçilerle ilgili iki sorun vardır (C ++ 11 öncesi):

  • standartlar içermediğinden, her kütüphane kendi (NIH sendromu ve bağımlılık sorunları)
  • potansiyel maliyet

Varsayılan bu maliyet ücretsiz olması ile akıllı işaretçi, bir unique_ptr. Ne yazık ki sadece son zamanlarda ortaya çıkan C ++ 11 hareket semantiği gerektirir. Diğer tüm akıllı işaretçilerin bir maliyeti ( shared_ptr, intrusive_ptr) vardır veya ideal anlambilimden ( auto_ptr) daha düşüktür .

C ++ 11 köşeyi dönünce, std::unique_ptrnihayet bittiğini düşünmek cazip olurdu ... Çok iyimser değilim.

Sadece birkaç büyük derleyici C ++ 11'in çoğunu ve sadece son sürümlerini uygular. QT ve Boost gibi büyük kütüphanelerin bir süre C ++ 03 ile uyumluluğu sürdürmeye istekli olmasını bekleyebiliriz, bu da yeni ve parlak akıllı işaretçilerin geniş bir şekilde benimsenmesini engeller.


12

Akıllı işaretçilerden uzak durmamalısınız, özellikle etrafından bir nesne geçirmeniz gereken uygulamalarda kullanımları vardır.

Kütüphaneler ya bir değer döndürme ya da bir nesneyi doldurma eğilimindedir. Genellikle birçok yerde kullanılması gereken nesneler yoktur, bu nedenle akıllı işaretçiler kullanmalarına gerek yoktur (en azından arayüzlerinde değil, dahili olarak kullanabilirler).

Örnek olarak üzerinde çalıştığımız bir kütüphaneyi ele alabilirim, birkaç aylık bir gelişimden sonra sadece birkaç sınıfta (tüm sınıfların% 3-5'i) işaretçiler ve akıllı işaretçiler kullandığımızı fark ettim.

Geçme referans olarak değişkenleri biz bizi zorladı kullanılan bir kitaplık olduğunu biz boş olabilir nesneyi ve ham işaretçileri vardı her çoğu yerde yeterli oldu, biz akıllı işaretçiler kullandı.

Düzenleme (İtibarım nedeniyle yorum yapamıyorum): değişkenleri başvuru ile iletmek çok esnektir: Nesnenin salt okunur olmasını istiyorsanız, bir const referansı kullanabilirsiniz (nesneyi yazabilmek için hala bazı kötü dökümler yapabilirsiniz ) ancak mümkün olan en yüksek korumayı elde edersiniz (akıllı işaretçilerle aynıdır). Ama sadece nesneyi iade etmenin çok daha hoş olduğunu kabul ediyorum.


Sizinle tam olarak aynı fikirde değilim, ancak çoğu durumda değişken referansların geçişini reddeden bir düşünce okulu olduğuna dikkat çekeceğim . İtiraf ediyorum o okula bağlı olduğumu. Fonksiyonların argümanlarını değiştirmemesini tercih ederim. Her halükarda, bildiğim kadarıyla, C ++ 'ın değişken referansları, referans aldıkları nesnelerin yanlış kullanılmasını önlemek için hiçbir şey yapmaz, bu da akıllı işaretçilerin yapmayı amaçladığı şeydir.
thb

2
Bunun için const var (Ben yorum yapabilir gibi görünüyor: D).
Robot Mess

9

Qt, Java olmaya çalışmak için Standart kütüphanenin birçok bölümünü gereksiz yere yeniden icat etti. Şimdi kendi akıllı işaretçileri olduğuna inanıyorum, ancak genel olarak, tasarımın zirvesi değil. wxWidgets, bildiğim kadarıyla, kullanılabilir akıllı işaretçiler yazılmadan çok önce tasarlandı.

Boost'a gelince, uygun olan yerlerde akıllı işaretçiler kullanmasını tamamen bekliyorum. Daha spesifik olmanız gerekebilir.

Ayrıca, mülkiyeti uygulamak için akıllı işaretçilerin bulunduğunu unutmayın. API'nın sahiplik semantiği yoksa, neden akıllı işaretçi kullanılsın?


19
Qt, işlevselliğin büyük bir kısmı kullanmak istediği platformlarda yeterince yaygın hale getirilmeden önce yazılmıştır. Uzun zamandır akıllı göstergeleri vardı ve bunları neredeyse tüm Q * sınıflarında örtük kaynak paylaşımını yapmak için kullanıyor.
rubenvb

6
Her GUI kütüphanesi gereksiz yere tekerleği yeniden icat eder. Dizelerde bile, Qt QString, wxWidgets wxString, MFC korkunç bir şekilde adlandırılır CString. UTF-8 std::string, GUI görevlerinin% 99'u için yeterince iyi değil mi?
Ters

10
@Inverse QString, std :: string çevresinde olmadığında oluşturuldu.
MrFox

Qt'nin ne zaman oluşturulduğunu ve o sırada hangi akıllı işaretçilerin mevcut olduğunu kontrol edin.
Dainius

3

İyi soru. Bahsettiğiniz belirli makaleleri bilmiyorum, ama zaman zaman benzer şeyler okudum. Şüphem, bu tür makalelerin yazarlarının C ++ tarzı programlamaya karşı bir önyargıya sahip olma eğilimindedir. Eğer yazar sadece gerekli olduğunda C ++ 'da programlarsa, Java'ya ya da olabildiğince çabuk geri dönerse, C ++ zihniyetini gerçekten paylaşmaz.

Aynı yazarların bir kısmının veya çoğunun çöp toplama bellek yöneticilerini tercih ettiğinden şüpheleniliyor. Yapmıyorum ama benden farklı düşünüyorum.

Akıllı işaretçiler harika, ancak referans sayılarını tutmak zorundalar. Referans sayımlarının tutulması, çalışma zamanında maliyetleri - genellikle mütevazı maliyetleri, ancak yine de maliyetleri - taşır. Özellikle işaretçiler yıkıcılar tarafından yönetiliyorsa, çıplak işaretçiler kullanarak bu maliyetlerden tasarruf etmek yanlış bir şey değildir.

C ++ ile ilgili mükemmel şeylerden biri, gömülü sistem programlama desteğidir. Çıplak işaretçilerin kullanımı bunun bir parçasıdır.

Güncelleme: Bir yorumcu, C ++ 'ın yeni unique_ptr(TR1'den beri mevcut) referansları saymadığını doğru bir şekilde gözlemledi . Yorum yapan kişinin aklımdakinden farklı "akıllı işaretçi" tanımı da vardır. Tanım konusunda haklı olabilir.

Ek güncelleme: Aşağıdaki yorum dizisi yanıyor. Hepsinin okunması tavsiye edilir.


2
Başlangıç ​​olarak, gömülü sistem programlaması tüm programlamanın büyük bir azınlığıdır ve oldukça önemsizdir. C ++ genel amaçlı bir dildir. İkincisi, shared_ptrreferans sayısını tutar. Referans sayısını hiç tutmayan birçok akıllı işaretçi türü vardır. Son olarak, bahsi geçen kütüphaneler, bol miktarda kaynağa sahip platformlara yöneliktir. Ben downvoter olduğum için değil, ama tek söylediğim gönderinin yanlış dolu olması.
Köpek yavrusu

2
@ thb - Sana duyarlılıkla katılıyorum. DeadMG - Lütfen gömülü sistemler olmadan yaşamaya çalışın. Evet - bazı akıllı işaretçilerin ek yükü yoktur, ancak bazılarında ek yük vardır. OP kütüphanelerden bahseder. Örneğin Boost, gömülü sistemler tarafından kullanılan parçalara sahiptir - ancak akıllı işaretçiler belirli uygulamalar için uygun olmayabilir.
Ed Heal

2
@EdHeal: Gömülü sistemler olmadan yaşamamak! = Onlar için programlama küçük, alakasız bir azınlık değildir. Akıllı işaretçiler, bir kaynağın ömrünü yönetmeniz gereken her durum için uygundur.
Köpek yavrusu

4
shared_ptrek yükü yoktur. Yalnızca, iş parçacığı için güvenli paylaşılan sahiplik semantiğine ihtiyacınız yoksa ek yükü vardır;
R. Martinho Fernandes

1
Hayır, shared_ptr, iş parçacığı için güvenli paylaşılan sahiplik semantiği için gereken minimum değerin üzerinde önemli bir ek yüke sahiptir; özellikle, yalnızca yeniden sayımı depolamak amacıyla, paylaştığınız gerçek nesneden ayrı bir yığın bloğu tahsis eder. intrusive_ptr daha verimlidir, ancak (shared_ptr gibi) nesneye yönelik her göstergenin bir intrusive_ptr olacağını varsayar. Uygulamamda yaptığım gibi, özel bir ref sayma paylaşılan işaretçisiyle intrusive_ptr'den daha düşük ek yük elde edebilir ve daha sonra en az bir akıllı işaretçinin T * değerini aşacağını garanti edebildiğinizde T * kullanabilirsiniz.
Qwertie

2

Başka akıllı işaretçiler de vardır. Ağ çoğaltması gibi bir şey için özel bir akıllı işaretçi isteyebilirsiniz (erişilip erişilmediğini algılayan ve sunucuya herhangi bir değişiklik gönderen veya benzeri bir şey), değişikliklerin geçmişini tutar, erişildiği gerçeğini işaretler, böylece ne zaman araştırılabilir verileri diske kaydedersiniz vb. İşaretçide bunu yapmanın en iyi çözüm olup olmadığından emin değilim, ancak yerleşik akıllı işaretçi türlerini kütüphanelerde kullanmak insanların kilitlenmesine ve esnekliğini kaybetmesine neden olabilir.

İnsanlar akıllı işaretçilerin ötesinde her türlü farklı bellek yönetimi gereksinimlerine ve çözümlerine sahip olabilir. Belleği kendim yönetmek isteyebilirim, bellek havuzundaki şeyler için yer ayırıyor olabilirim, bu yüzden önceden değil (oyunlar için yararlı). C ++ çöp toplama uygulaması kullanıyor olabilir (C ++ 11 bunu henüz yok olmasına rağmen mümkün kılar). Ya da belki de onlarla uğraşmaktan endişe edecek kadar gelişmiş bir şey yapmıyorum, başlatılmamış nesnelere unutamayacağımı biliyorum. Belki sadece işaretçi koltuk değneği olmadan hafızayı yönetme yeteneğime güveniyorum.

C ile entegrasyon da başka bir konudur.

Başka bir sorun, akıllı işaretçilerin STL'nin bir parçası olmasıdır. C ++, STL olmadan kullanılabilecek şekilde tasarlanmıştır.


" Başka bir sorun akıllı işaretçiler STL bir parçasıdır. " Onlar değil.
curiousguy

0

Ayrıca hangi alanda çalıştığınıza da bağlıdır. Yaşamak için oyun motorları yazıyorum, veba gibi takviyeden kaçınıyoruz, oyunlarda destek yükü kabul edilemez. Çekirdek motorumuzda kendi stl versiyonumuzu yazdık (ea stl gibi).

Ben bir form uygulaması yazmak olsaydı, akıllı işaretçiler kullanmayı düşünebilirsiniz; ancak bellek yönetimi ikinci kez yapıldığında, bellek üzerinde ayrıntılı bir kontrole sahip olmama, can sıkıcı bir hal alır.


3
"Güçlendirme yükü" diye bir şey yoktur.
curiousguy

4
Paylaşılan_ptr oyun motorumu kayda değer ölçüde yavaşlatmadım. Yine de üretim ve hata ayıklama sürecini hızlandırdılar. Ayrıca, "destek yükü" ile tam olarak ne demek istiyorsun? Bu dökmek için oldukça büyük bir battaniye.
derpface

@curiousguy: Tüm bu üstbilgilerin ve makro + şablon
voodoo'nun
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.