Std'den miras kalmayacaksın :: vektör


189

Tamam, itiraf etmek gerçekten zor, ama şu anda miras almak için güçlü bir cazibem var std::vector.

Vektör için yaklaşık 10 özelleştirilmiş algoritmaya ihtiyacım var ve bunların doğrudan vektörün üyesi olmasını istiyorum. Ama doğal olarak geri kalan std::vectorarayüzüne de sahip olmak istiyorum . Yasalara uyan bir vatandaş olarak ilk fikrim sınıfta bir std::vectorüyeye sahip MyVectorolmaktı. Ama sonra tüm std :: vector arabirimini el ile yeniden oluşturmak zorunda kalacaktım. Yazmak için çok fazla. Daha sonra, özel miras hakkında düşündüm, böylece yöntemleri yeniden sunmak yerine using std::vector::membergenel bölüme bir sürü yazarım . Bu aslında sıkıcı.

Ve işte buradayım, sadece halka açık bir şekilde miras alabileceğimi düşünüyorum std::vector, ancak dokümantasyonda bu sınıfın polimorfik olarak kullanılmaması gerektiğine dair bir uyarı sağlıyorum. Çoğu geliştiricinin bunun polimorfik olarak kullanılmaması gerektiğini anlayacak kadar yetkin olduğunu düşünüyorum.

Kararım kesinlikle haksız mı? Öyleyse neden? Ek üyelerin gerçekten üyelerine sahip olacak, ancak vektörün tüm arayüzünün yeniden yazılmasını gerektirmeyecek bir alternatif sağlayabilir misiniz ? Bundan şüphe ediyorum, ama yapabilirsen mutlu olurum.

Ayrıca, bazı aptalların şöyle bir şey yazabilmeleri dışında

std::vector<int>* p  = new MyVector

MyVector'u kullanmanın başka gerçekçi tehlikesi var mı? Gerçekçi diyerek, vektörü göstermek için bir işaretçi gerektiren bir işlevi hayal etmek gibi şeyleri atıyorum ...

Benim durumumu ifade ettim. Günah işledim. Şimdi beni affetmek ya da affetmemek size kalmış :)


9
Yani, temel olarak, kabın arayüzünü yeniden uygulamak için çok tembel olduğunuz gerçeğine dayanarak ortak bir kuralı ihlal etmenin uygun olup olmadığını mı soruyorsunuz? O zaman hayır, öyle değil. Acı hapı yutar ve düzgün bir şekilde yaparsanız, her iki dünyanın da en iyisini elde edebilirsiniz. O adam olma. Sağlam kod yazın.
Jim Brissom

7
Neden üye olmayan işlevlerle ihtiyacınız olan işlevselliği eklemek istemiyorsunuz / istemiyorsunuz? Bana göre, bu senaryoda yapılacak en güvenli şey bu.
Simone

11
@Jim: std::vector'in arayüzü oldukça büyük ve C ++ 1x ortaya çıktığında, büyük ölçüde genişleyecek. Bu yazılacak çok şey ve birkaç yıl içinde daha da genişleyecek. Bence bu, kontrol altına almak yerine kalıtımı dikkate almak için iyi bir neden - eğer bu işlevlerin üye olması gerektiği fikrini takip ederseniz (şüpheliyim). STL kaplarından türetmeme kuralı, polimorfik olmamalarıdır. Bunları bu şekilde kullanmıyorsanız, geçerli değildir.
sbi

9
Sorunun asıl et bir cümlede: "Onları doğrudan vektörün üyesi olmalarını istiyorum". Sorudaki başka hiçbir şey gerçekten önemli değil. Bunu neden "istiyorsun"? Sadece üye olmayanlar olarak bu işlevselliği sağlamakla ilgili sorun nedir?
jalf

8
@JoshC: "Sen" her zaman "sen" daha yaygın olmuştur, ve aynı zamanda King James İncil bulunan sürümü (ki genellikle insanların yazarken ne ima "değil" [sen] "). Yeryüzünde size buna "yazım hatası" demenize ne sebep olur?
ruakh

Yanıtlar:


155

Aslında, halkın mirasıyla ilgili yanlış bir şey yok std::vector. Buna ihtiyacınız varsa, sadece yapın.

Bunu sadece gerçekten gerekli olduğunda yapmayı öneririm . Sadece serbest fonksiyonlar ile istediğinizi yapamıyorsanız (örn. Bazı durumları korumalısınız).

Sorun şu ki MyVectoryeni bir varlık. Yeni bir C ++ geliştiricisi kullanmadan önce ne olduğunu bilmelidir. Arasındaki fark nedir std::vectorve MyVector? Hangisi burada ve orada kullanmak daha iyidir? Ne taşımanız gerekiyorsa, std::vectorhiç MyVector? Sadece kullanabilir miyim swap()değil miyim?

Sadece daha iyi görünmesi için bir şeyler yapmak için yeni varlıklar üretmeyin. Bu varlıklar (özellikle böyle yaygın) vakumda yaşamayacaklar. Sürekli artan entropi ile karışık bir ortamda yaşayacaklar.


7
Benim tek karşıt yönüm, bunun için ne yaptığını gerçekten bilmesi gerektiğidir. Örneğin, yok içine ilave veri üyelerini tanıtmak MyVectorve daha sonra kabul işlevlere de geçmek deneyin std::vector&ya std::vector*. Std :: vector * veya std :: vector & kullanarak herhangi bir kopya ataması varsa, yeni veri üyelerinin MyVectorkopyalanmayacağı dilimleme sorunlarımız vardır . Aynı şey, bir temel işaretçi / referans üzerinden takas çağırmak için de geçerlidir. Nesne dilimlemeyi riske atan her türlü kalıtım hiyerarşisinin kötü olduğunu düşünüyorum.
stinky472

13
std::vectoryıkıcısı değil virtual, bu yüzden asla miras kalmamalısınız
André Fratelli

2
Ben bu nedenle halka açık std :: vector miras bir sınıf yarattı: STL olmayan bir vektör sınıfı ile eski kod vardı ve ben STL taşımak istedim. Std :: vector kullanarak yeni kod yazarken eski sınıfı std :: vector türetilmiş bir sınıf olarak yeniden uyguladım, eski kodda eski işlev adlarını (örneğin, size () yerine Count ()) kullanmaya devam etmeme izin verdim fonksiyonlar. Herhangi bir veri üyesi eklemedim, bu nedenle std :: vector yıkıcı yığın üzerinde oluşturulan nesneler için iyi çalıştı.
Graham Asher

3
@GrahamAsher Bir nesneyi imlecin tabanından silerseniz ve yıkıcı sanal değilse, programınız tanımlanmamış davranış gösteriyorsa. Tanımlanmamış davranışın olası sonuçlarından biri "testlerimde iyi sonuç verdi". Başka bir şey, büyükannenize web tarama geçmişinizi e-postayla göndermesidir. Her ikisi de C ++ standardıyla uyumludur. Derleyicilerin, işletim sistemlerinin veya ayın fazının nokta sürümleri ile birinden diğerine geçiş de uyumludur.
Yakk - Adam Nevraumont

2
@GrahamAsher Hayır, herhangi bir nesneyi sanal bir yıkıcı olmadan temel almak için bir işaretçiden sildiğinizde, bu standart altında tanımlanmamış bir davranıştır. Ne olduğunu düşündüğünüzü anlıyorum; sadece yanlış olursun. "temel sınıf yıkıcı denir ve çalışır" bu tanımsız davranışın olası bir belirtisidir (ve en yaygın), çünkü bu derleyicinin genellikle oluşturduğu saf makine kodu. Bu onu güvenli kılmaz, ne de harika bir fikirdir.
Yakk - Adam Nevraumont

92

Tüm STL, algoritmalar ve kaplar ayrı olacak şekilde tasarlanmıştır .

Bu, farklı yineleyiciler kavramlarına yol açtı: const yineleyiciler, rastgele erişim yineleyiciler, vb.

Bu nedenle, bu kuralı kabul etmenizi ve algoritmalarınızı üzerinde çalıştıkları kapın ne olduğunu umursamayacakları şekilde tasarlamanızı öneririm - ve yalnızca kendi gerçekleştirmeleri gereken belirli bir yineleyici türü gerektirirler. operasyonlar.

Ayrıca, sizi Jeff Attwood'un bazı iyi sözlerine yönlendirmeme izin verin .


63

std::vectorHalka açık bir şekilde miras kalmamasının ana nedeni , torunların polimorfik kullanımını etkili bir şekilde engelleyen sanal bir yıkıcı olmamasıdır. Özellikle, olan izin verilmeyen için deletebir std::vector<T>*türetilmiş bir nesneye (türetilmiş sınıf hiç üyesi ekler bile) o aslında noktaları, henüz derleyici genellikle bu konuda sizi uyarabilir olamaz.

Bu koşullar altında özel mirasa izin verilir. Bu nedenle, özel kalıtım yöntemlerini kullanmanızı ve aşağıda gösterildiği gibi ebeveynden gerekli yöntemleri iletmenizi öneririm.

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};

Öncelikle, üzerinde çalıştıkları kap türünü soyutlamak için algoritmalarınızı yeniden düzenlemeyi düşünmeli ve cevaplayanların çoğunluğunun işaret ettiği gibi serbest şablonlu fonksiyonlar olarak bırakmalısınız. Bu genellikle bir algoritmanın bağımsız değişken olarak konteyner yerine bir çift yineleyiciyi kabul etmesiyle yapılır.


IIUC, sanal bir yıkıcının yokluğu, ancak türetilmiş sınıfın yıkım üzerine serbest bırakılması gereken kaynakları tahsis etmesi durumunda bir sorundur. (Polimorfik bir kullanım durumunda serbest bırakılmayacaklardır, çünkü farkında olmadan türetilmiş bir nesnenin sahipliğini imlecin tabanına taşıyorsa, zaman geldiğinde temel yıkıcıyı çağırır.) Diğer geçersiz kılınan üye işlevlerinden benzer sorunlar ortaya çıkar. üslerin çağrı yapmak için geçerli olduğu kabul edilmelidir. Ancak ek kaynaklar yok, başka nedenler var mı?
Peter - Monica'yı eski

2
vector'nin ayrılmış depolaması sorun değil - sonuçta, vector' imha edici 'bir işaretçi aracılığıyla tamam çağrılacaktı vector. Bu standart yasakladığı bu sadece var deleteing serbest mağaza nesneleri bir temel sınıf ifadesi yoluyla. Bunun nedeni, (de) ayırma mekanizmasının, deleteörneğin belirli boyutlardaki nesneler için birkaç ayırma arenası olduğunda , (de) ayırma mekanizmasının bellek yığınının büyüklüğünü işlenenden çıkarmak üzere çıkarmaya çalışabilmesidir . Bu kısıtlama, afaikler, statik veya otomatik saklama süresine sahip nesnelerin normal imhası için geçerli değildir.
Peter - Monica'yı

@DavisHerring Sanırım orada hemfikiriz :-).
Peter - Monica'yı eski

@DavisHerring Ah, görüyorum, ilk yorumuma atıfta bulunuyorsunuz - bu yorumda bir IIUC vardı ve bir soru ile sonuçlandı; Daha sonra gördüm ki aslında her zaman yasak. (Basilevs, "etkili bir şekilde önler" diye genel bir açıklama yapmıştı ve ben bunu önleme şeklini merak ettim.) Evet, kabul ediyoruz: UB.
Peter - Monica'yı eski

@Basilevs Bu yanlışlıkla olmalı. Sabit.
ThomasMcLeod

36

Bunu düşünüyorsanız, ofisinizdeki dil bilgiçlerini çoktan öldürdünüz. Onları yoldan çekerken, neden sadece

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

Bu, MyVector sınıfınızı yanlışlıkla uyandırmaktan kaynaklanabilecek tüm olası blundersları ortadan kaldıracak ve sadece biraz ekleyerek tüm vektör op'larına erişebilirsiniz .v.


Ve kapsayıcıları ve algoritmaları ortaya çıkarmak? Yukarıda Kos'un cevabına bakınız.
bruno nery


19

Neyi başarmayı umuyorsun? Sadece bazı işlevler mi sağlıyorsunuz?

Bunu yapmanın C ++ deyimsel yolu, sadece işlevselliği uygulayan bazı ücretsiz işlevler yazmaktır. Muhtemelen bir std :: vector'a, özellikle uyguladığınız işlevsellik için ihtiyaç duymamanız , yani std :: vector'dan miras almaya çalışarak yeniden kullanılabilirliği kaybettiğiniz anlamına gelir.

Standart kütüphaneye ve başlıklara bakmanızı ve nasıl çalıştıklarını düşünmenizi şiddetle tavsiye ederim.


5
İkna olmadım. Nedenini açıklamak için önerilen kodlardan bazılarını güncelleyebilir misiniz?
Karl Knechtel

6
@Ermen: estetik dışında, bunun iyi bir nedeni var mı?
snemarch

12
@Ermen: Daha iyi estetik ve daha fazla jeneriklik, özgürlüğü frontve backişlevleri de sağlamak olacaktır . :) (Ayrıca ücretsiz beginve endC ++ 0x ve boost örneğini de düşünün .)
UncleBens

3
Hâlâ serbest fonksiyonlarda neyin yanlış olduğunu bilmiyorum. Eğer STL'nin “estetiğini” sevmiyorsanız, belki C ++ sizin için estetik olarak yanlış bir yerdir. Bazı üye işlevlerin eklenmesi sorunu çözmez, çünkü diğer algoritmalar hala ücretsiz işlevlerdir.
Frank Osterfeld

17
Harici bir algoritmada yoğun çalışma sonucu önbelleklemek zordur. Vektördeki tüm elemanların toplamını hesaplamanız veya vektör elemanları ile bir polinom denklemini katsayılar olarak çözmeniz gerektiğini varsayalım. Bu operasyonlar ağırdır ve tembellik onlar için yararlı olacaktır. Ancak kabı sarmadan veya miras almadan tanıtamazsınız.
Basilevs

14

Bence çok az kural zamanın% 100'üne körü körüne uyulmalıdır. Oldukça fazla düşünce vermiş gibi görünüyorsunuz ve bunun yolun olduğuna ikna oldunuz. Yani - birisi bunu yapmamak için iyi spesifik nedenlerle gelmedikçe - bence planınıza devam etmelisiniz.


9
İlk cümleniz% 100 doğrudur. :)
Steve Fallows

5
Ne yazık ki, ikinci cümle değil. Çok fazla düşünce vermedi. Sorunun çoğu ilgisiz. Motivasyonunu gösteren tek kısmı "Onları doğrudan vektörün üyesi olmalarını istiyorum". İstiyorum. İçin bir sebep yok neden bu arzu edilir. Sanki hiç düşünmemiş gibi geliyor .
jalf

7

Biri std::vectorfarklı çalışan bir sınıf yapmak istemedikçe, tanımının std::vectorgizli detaylarını kendi yöntemiyle ele almadığı std::vectorveya bu sınıfın nesnelerini yerine kullanmak için ideolojik nedenleri olmadığı sürece miras almak için hiçbir neden yoktur . std::vectorolanlar. Bununla birlikte, C ++ standardının yaratıcıları, std::vectorbu tür kalıtsal sınıfın vektörü belirli bir şekilde geliştirmek için yararlanabileceği herhangi bir arabirim (korumalı üyeler şeklinde) sağlamadı. Aslında, ek uygulamaya ihtiyaç duyabilecek veya ek uygulamaya ince ayar yapabilecek belirli bir yönü düşünmenin hiçbir yolu yoktu, bu nedenle herhangi bir amaç için böyle bir arayüz sağlamayı düşünmeleri gerekmiyordu.

İkinci seçeneğin nedenleri sadece ideolojik olabilir, çünkü std::vectorbunlar polimorfik değildir ve aksi takdirde std::vectorkamu arayüzünü kamu mirası veya kamu üyeliği yoluyla ortaya çıkarmanız arasında bir fark yoktur . (Serbest işlevlerden kurtulamadığınız için nesnenizde bir durum tutmanız gerektiğini varsayalım). Daha az sağlam bir notta ve ideolojik bakış açısından, std::vectors'nin bir tür "basit fikir" olduğu anlaşılmaktadır , bu nedenle yerlerinde farklı olası sınıfların nesneleri biçimindeki herhangi bir karmaşıklık ideolojik olarak hiçbir işe yaramaz .


Mükemmel cevap. SO hoş geldiniz!
Armen Tsirunyan

4

Pratik anlamda: Türetilmiş sınıfınızda veri üyeleriniz yoksa, polimorfik kullanımda bile herhangi bir sorununuz olmaz. Sanal bir yıkıcıya yalnızca temel sınıfın ve türetilmiş sınıfın boyutları farklıysa ve / veya sanal işlevleriniz varsa (v-tablosu anlamına gelir) ihtiyacınız vardır.

AMA teoride: C ++ 0x FCD'deki [expr.delete] 'den: İlk alternatifte (nesneyi sil), silinecek nesnenin statik türü dinamik türünden farklıysa, statik tür bir silinecek nesnenin dinamik türünün temel sınıfı ve statik tür sanal bir yıkıcıya sahip olacak veya davranış tanımsız olacaktır.

Ancak std :: vector'dan problemsiz bir şekilde türetebilirsiniz. Aşağıdaki kalıbı kullandım:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...

3
"Sadece temel sınıfın ve türetilmiş sınıfın boyutları farklıysa / veya sanal işlevleriniz varsa (yani bir v-tablosu anlamına gelir) bir sanal yıkıcıya ihtiyacınız vardır." Bu iddia pratik olarak doğru, ancak teorik olarak değil
Armen Tsirunyan

2
evet, prensip olarak hala tanımlanmamış bir davranıştır.
jalf

Bunun tanımsız bir davranış olduğunu iddia ederseniz, bir kanıt görmek istiyorum (standarttan alıntı).
hmuelner

8
@hmuelner: Ne yazık ki, Er ve jalf bu konuda doğrudur. Gönderen [expr.delete]C ++ 0x FCD içinde: Silinecek nesnenin statik tip dinamik türünden farklı ise <alıntı> birinci alternatifte (silme nesne) olarak, statik tip dinamik tip bir taban sınıfı olacaktır silinir ve statik tür sanal bir yıkıcıya sahip olur veya davranış tanımsızdır. </quote>
Ben Voigt

1
Bu komik, çünkü aslında davranışın önemsiz olmayan bir yıkıcıya (özellikle POD sınıflarının bir işaretçi-taban yoluyla yok edilebileceğine) bağlı olduğunu düşündüm.
Ben Voigt

3

İyi C ++ stilini izlerseniz, sanal işlevin olmaması sorun değildir, dilimlenir (bkz. Https://stackoverflow.com/a/14461532/877329 )

Sanal işlevlerin olmaması neden sorun değil? Çünkü bir işlev, sahip olduğu deleteherhangi bir işaretçiyi denememelidir , çünkü onun sahipliği yoktur. Bu nedenle, sıkı sahiplik politikalarını izliyorsa, sanal yıkıcılara ihtiyaç duyulmamalıdır. Örneğin, bu her zaman yanlıştır (sanal yıkıcı ile veya sanal yıkıcı olmadan):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    delete obj;
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj); //Will crash here. But caller does not know that
//  ...
    }

Buna karşılık, bu her zaman çalışır (sanal yıkıcı ile veya sanal yıkıcı olmadan):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj);
//  The correct destructor *will* be called here.
    }

Nesne bir fabrika tarafından oluşturulmuşsa delete, fabrika kendi yığınını da kullanabileceğinden, çalışan bir siliciye bir işaretçi döndürmelidir . Arayan bir share_ptrveya şeklinde olabilir unique_ptr. Kısacası, yapma deletesen alamadım şey doğrudan gelen new.


2

Evet, güvenli olmayan şeyleri yapmamaya dikkat ettiğiniz sürece güvenlidir ... Hiç kimsenin yeni bir vektör kullandığını gördüğümü sanmıyorum, bu yüzden pratikte iyi olacaksınız. Ancak, bu c ++ ortak deyim değil ....

Algoritmaların ne olduğu hakkında daha fazla bilgi verebilir misiniz?

Bazen bir tasarıma sahip bir yola inersiniz ve daha sonra almış olabileceğiniz diğer yolları göremezsiniz - 10 yeni algoritma ile vektörel olmanız gerektiğini iddia ettiğiniz gerçeği benim için alarm çanları çalar - gerçekten 10 genel amaç var mı bir vektörün uygulayabileceği algoritmalar mı yoksa hem genel amaçlı vektör VE hem de uygulamaya özel fonksiyonlar içeren bir nesne mi yapmaya çalışıyorsunuz?

Kesinlikle bunu yapmaman gerektiğini söylemiyorum, sadece alarm çanları verdiğiniz bilgilerle çalıyor, bu da beni soyutlamalarınızda bir şeylerin yanlış olduğunu düşündürüyor ve ne elde etmenin daha iyi bir yolu olduğunu istemek.


2

Ayrıca std::vectorson zamanlarda miras aldım ve çok yararlı buldum ve şimdiye kadar herhangi bir sorun yaşamadım.

Sınıfım seyrek bir matris sınıfı, yani matris öğelerimi bir yerde, yani std::vector. Devralma nedenim, tüm yöntemlere arabirimler yazmak için biraz fazla tembel olduğum ve ayrıca iyi bir arayüz kodunun bulunduğu SWIG aracılığıyla Python'a sınıfı arabirimliyorum std::vector. Sıfırdan yeni bir tane yazmak yerine bu arayüz kodunu sınıfıma genişletmeyi çok daha kolay buldum.

Ben yaklaşımla görebilirsiniz tek sorun bu kadar sanal olmayan yıkıcının ile değil, daha ziyade gibi ben aşırı etmek istiyorum diğer bazı yöntemler, push_back(), resize(), insert()vb Özel miras gerçekten iyi bir seçenek olabilir.

Teşekkürler!


10
Deneyimlerime göre, en uzun süreli en büyük hasara genellikle kötü tavsiye edilen bir şey deneyen ve " şimdiye kadar onunla ilgili herhangi bir sorun yaşamamış ( fark edildi ) " denen insanlar neden olmaktadır .
Hayal kırıklığına uğramış

0

Burada, istediğinizi yapmak için 2 yol daha sunmama izin verin. Biri sarmanın başka bir yoludur std::vector, diğeri kullanıcılara herhangi bir şeyi kırma şansı vermeden miras almanın yoludur:

  1. std::vectorÇok fazla işlev sarmalayıcısı yazmadan başka bir sarma yolu eklememe izin verin .

#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. Dtor problemi yerine std :: span kaynağından devralma std::vectorve bu sorunu önleme.

0

Bu sorunun nefessiz inci-kavrama üretmesi garanti edilir, ancak aslında Standart bir konteynırdan türetilmekten kaçınmak veya kaçınmak için "gereksiz yere çoğaltmak" için savunulabilir bir neden yoktur. Mümkün olan en basit, en kısa ifade en açık ve en iyisidir.

Herhangi bir türetilmiş türün etrafında normal bakım yapmanız gerekir, ancak Standarttan bir baz vakası hakkında özel bir şey yoktur. Bir temel üye işlevinin geçersiz kılınması zor olabilir, ancak bu sanal olmayan herhangi bir temelle yapmak mantıksız olurdu, bu yüzden burada çok özel değil. Bir veri üyesi ekleyecekseniz, üyenin tabanın içeriğiyle tutarlı tutulması gerekiyorsa dilimleme konusunda endişelenmeniz gerekir, ancak yine de herhangi bir taban için aynıdır.

Standart bir kaptan türetildiğini özellikle yararlı bulduğum yer, diğer inşaatçılar tarafından karışıklık veya kaçırma şansı olmadan tam olarak gereken başlatmayı yapan tek bir kurucu eklemektir. (Sana bakıyorum, initialization_list yapıcıları!) Sonra, ortaya çıkan nesneyi özgürce kullanabilirsiniz, dilimlenmiş - üssü bekleyen bir şeye atıfta bulunarak, üssün bir örneğine taşıyın, ne var. Türetilmiş sınıfa bir şablon bağımsız değişkenini bağlamanız sizi rahatsız etmedikçe, endişelenmeniz gereken son durumlar yoktur.

Bu tekniğin C ++ 20'de hemen faydalı olacağı bir yer rezervasyondur. Nerede yazabilirdik

  std::vector<T> names; names.reserve(1000);

söyleyebiliriz

  template<typename C> 
  struct reserve_in : C { 
    reserve_in(std::size_t n) { this->reserve(n); }
  };

ve sonra, sınıf üyeleri olarak bile,

  . . .
  reserve_in<std::vector<T>> taken_names{1000};  // 1
  std::vector<T> given_names{reserve_in<std::vector<T>>{1000}}; // 2
  . . .

(tercihe göre) ve sadece rezerv () çağırmak için bir kurucu yazmanıza gerek yoktur.

( reserve_inTeknik olarak, C ++ 20'yi beklemesi gerekmesinin nedeni, önceki Standartların boş bir vektörün kapasitesinin hareketler boyunca korunmasını gerektirmemesidir. Bu bir gözetim olarak kabul edilir ve makul olarak düzeltilmesi beklenebilir. Ayrıca, düzeltmenin, önceki Standartlara etkili bir şekilde desteklenmesini de bekleyebiliriz, çünkü mevcut tüm uygulamalar aslında hareketler boyunca kapasiteyi korur; Standartlar sadece gerekli değildir. silah rezervi neredeyse her zaman sadece bir optimizasyon.)

Bazıları davanın reserve_inbir serbest işlev şablonu tarafından daha iyi sunulduğunu savunur :

  template<typename C> 
  auto reserve_in(std::size_t n) { C c; c.reserve(n); return c; }

Böyle bir alternatif kesinlikle uygulanabilir - ve hatta bazen * RVO nedeniyle sonsuz derecede daha hızlı olabilir. Ancak, türetme veya serbest fonksiyon seçimi, Standart bileşenlerden türetme hakkındaki temelsiz (heh!) Batıl inançlardan değil, kendi yararları üzerine yapılmalıdır. Yukarıdaki örnek kullanımda, yalnızca ikinci form serbest işlevle çalışır; sınıf bağlamının dışında olmasına rağmen biraz daha kısaca yazılabilir:

  auto given_names{reserve_in<std::vector<T>>(1000)}; // 2
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.