C ++ özel mirası ne zaman kullanmalıyım?


116

Korunan kalıtımın aksine, C ++ özel kalıtım ana akım C ++ geliştirmede yolunu buldu. Ancak yine de bunun için iyi bir kullanım bulamadım.

Ne zaman kullanıyorsunuz?

c++  oop 

Yanıtlar:


60

Cevabı kabul ettikten sonra not: Bu tam bir cevap DEĞİLDİR. Soruyla ilgileniyorsanız, burada (kavramsal olarak) ve burada (hem teorik hem de pratik) gibi diğer yanıtları okuyun . Bu sadece özel mirasla elde edilebilecek süslü bir numaradır. Öyle olsa fantezi o sorunun cevabı değildir.

C ++ SSS'de gösterilen (başkalarının yorumlarında bağlantılı olan) yalnızca özel mirasın temel kullanımının yanı sıra, bir sınıfı mühürlemek (.NET terminolojisinde) veya bir sınıfı son haline getirmek (Java terminolojisinde) için özel ve sanal mirasın bir kombinasyonunu kullanabilirsiniz. . Bu yaygın bir kullanım değil, ama yine de ilginç buldum:

class ClassSealer {
private:
   friend class Sealed;
   ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{ 
   // ...
};
class FailsToDerive : public Sealed
{
   // Cannot be instantiated
};

Mühürlü somutlaştırılabilir. Bu kaynaklanmaktadır ClassSealer ve bir arkadaşı olarak doğrudan özel yapıcı çağırabilir.

FailsToDerive o çağırmalıdır olarak derlemek olmaz ClassSealer doğrudan yapıcı (sanal miras gereksinimi), ama buna özel değil olarak can Mühürlü sınıf ve bu durumda FailsToDerive arkadaş değildir ClassSealer .


DÜZENLE

Yorumlarda bunun CRTP kullanılarak jenerik hale getirilemeyeceği belirtilmişti. C ++ 11 standardı, şablon bağımsız değişkenleriyle arkadaş olmak için farklı bir sözdizimi sağlayarak bu sınırlamayı ortadan kaldırır:

template <typename T>
class Seal {
   friend T;          // not: friend class T!!!
   Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...

Elbette bunların hepsi tartışmalı, çünkü C ++ 11 finaltam da bu amaç için bağlamsal bir anahtar kelime sağlıyor:

class Sealed final // ...

Bu harika bir teknik. Üzerine bir blog yazısı yazacağım.

1
Soru: Sanal miras kullanmadıysak, FailsToDerive derleyecekti. Doğru?

4
+1. @Sasha: Doğru, sanal kalıtım gereklidir, çünkü en çok türetilmiş sınıf her zaman doğrudan miras alınan tüm sınıfların kurucularını doğrudan çağırır, bu düz kalıtım için geçerli değildir.
j_random_hacker

5
Bu, mühürlemek istediğiniz her sınıf için özel bir ClassSealer yapmadan jenerik yapılabilir! Şuna bakın: class ClassSealer {korumalı: ClassSealer () {}}; bu kadar.

+1 Iraimbilanja, çok havalı! BTW CRTP kullanımıyla ilgili önceki yorumunuzu (şimdi silindi) gördüm: Bence bu aslında işe yarıyor, şablon arkadaşları için sözdizimini doğru yapmak biraz zor. Ancak her durumda şablon olmayan çözümünüz çok daha harika :)
j_random_hacker

138

Ben her zaman kullanırım. Aklıma gelen birkaç örnek:

  • Bir temel sınıfın arayüzünün tamamını olmasa da bazılarını açığa çıkarmak istediğimde. Liskov ikame edilebilirliği bozulduğundan, kamusal miras bir yalan olacaktır , oysa kompozisyon bir dizi yönlendirme işlevi yazmak anlamına gelecektir.
  • Sanal bir yıkıcı olmadan somut bir sınıftan türetmek istediğimde. Genel miras, istemcileri tabana işaretçi aracılığıyla silmeye davet ederek tanımsız davranışı çağırır.

Tipik bir örnek, bir STL konteynerinden özel olarak türetilmesidir:

class MyVector : private vector<int>
{
public:
    // Using declarations expose the few functions my clients need 
    // without a load of forwarding functions. 
    using vector<int>::push_back;
    // etc...  
};
  • Bağdaştırıcı Modelini uygularken, Adapted sınıfından özel olarak miras almak, kapalı bir örneğe iletme zorunluluğunu ortadan kaldırır.
  • Özel bir arayüz uygulamak için. Bu genellikle Gözlemci Modeli ile ortaya çıkar. Tipik olarak Observer sınıfım, MyClass diyor ki, kendisini bir Konu ile abone yapar . Ardından, yalnızca Sınıfımın Sınıfım -> Gözlemci dönüşümünü yapması gerekir. Sistemin geri kalanının bunu bilmesine gerek yoktur, bu nedenle özel miras belirtilir.

4
@Krsna: Aslında sanmıyorum. Burada tek bir neden var: sonuncusu dışında, etrafta çalışmak daha zor olacak tembellik.
Matthieu M.

11
Çok fazla tembellik değil (iyi anlamda kastetmedikçe). Bu, herhangi bir ekstra çalışma olmaksızın ortaya çıkan yeni fonksiyon aşırı yüklerinin yaratılmasına izin verir. C ++ 1x'te 3 yeni aşırı yükleme eklerlerse push_back, MyVectorbunları ücretsiz alır.
David Stone

@DavidStone, bunu bir şablon yöntemiyle yapamaz mısın?
Julien__

5
@Julien__: Evet, yazabilirsin template<typename... Args> constexpr decltype(auto) f(Args && ... args) noexcept(noexcept(std::declval<Base &>().f(std::forward<Args>(args)...)) and std::is_nothrow_move_constructible<decltype(std::declval<Base &>().f(std::forward<Args>(args)...))>) { return m_base.f(std::forward<Args>(args)...); }veya kullanarak yazabilirsin Base::f;. Özel kalıtımın ve bir usingifadenin size sağladığı işlevsellik ve esnekliğin çoğunu istiyorsanız, her işlev için o canavara sahipsiniz (ve unutmayın constve volatileaşırı yükler!).
David Stone

2
İşlevselliğin çoğunu söylüyorum çünkü hala using deyimi sürümünde bulunmayan fazladan bir hareket yapıcısını çağırıyorsunuz. Genel olarak, bunun optimize edilmesini beklersiniz, ancak işlev teorik olarak değerine göre hareketli olmayan bir türü döndürüyor olabilir. Yönlendirme işlevi şablonunun ayrıca fazladan bir şablon somutlaştırması ve constexpr derinliği vardır. Bu, programınızın uygulama sınırlarına girmesine neden olabilir.
David Stone

31

Özel mirasın kanonik kullanımı, ilişkinin "bağlamında uygulanmış" şeklidir (bu ifade için Scott Meyers'in "Etkili C ++" sayesinde). Başka bir deyişle, miras alan sınıfın dış arabiriminin devralınan sınıfla (görünür) ilişkisi yoktur, ancak işlevselliğini uygulamak için dahili olarak kullanır.


6
Bu durumda kullanılmasının nedenlerinden birinden bahsetmeye değer olabilir: Bu, boş temel sınıf optimizasyonunun gerçekleştirilmesine izin verir, bu, sınıf bir temel sınıf yerine bir üye olsaydı gerçekleşmez.
jalf

2
asıl kullanımı, örneğin politika kontrollü dizi sınıflarında veya sıkıştırılmış çiftlerde gerçekten önemli olduğu yerlerde alan tüketimini azaltmaktır. aslında boost :: compressed_pair korumalı kalıtım kullandı.
Johannes Schaub - litb

jalf: Hey, bunun farkında değildim. Bir sınıfın korumalı üyelerine erişmeniz gerektiğinde, halka açık olmayan mirasın esas olarak bir hack olarak kullanıldığını düşündüm. Yine de kompozisyon kullanılırken boş bir nesnenin neden herhangi bir yer kapladığını merak ediyorum. Muhtemelen evrensel adreslenebilirlik için ...

3
Bir sınıfı kopyalanamaz hale getirmek de kullanışlıdır - sadece kopyalanamayan boş bir sınıftan özel olarak miras alın. Artık, özel bir kopya oluşturucu ve atama operatörü tanımlamadan, bildirme işinden geçmek zorunda değilsiniz. Meyers de bundan bahsediyor.
Michael Burr

Bu sorunun aslında korumalı miras yerine özel mirasla ilgili olduğunu fark etmemiştim. evet sanırım bunun için epeyce uygulama var. yine de korumalı kalıtım için pek çok örnek düşünemiyorum: / bu sadece nadiren yararlı görünüyor.
Johannes Schaub -

23

Özel kalıtımın yararlı bir kullanımı, bir arabirim uygulayan ve daha sonra başka bir nesneyle kaydedilen bir sınıfa sahip olduğunuz zamandır. Bu arabirimi özel yaparsınız, böylece sınıfın kendisinin kaydolması gerekir ve yalnızca kayıtlı olduğu belirli nesne bu işlevleri kullanabilir.

Örneğin:

class FooInterface
{
public:
    virtual void DoSomething() = 0;
};

class FooUser
{
public:
    bool RegisterFooInterface(FooInterface* aInterface);
};

class FooImplementer : private FooInterface
{
public:
    explicit FooImplementer(FooUser& aUser)
    {
        aUser.RegisterFooInterface(this);
    }
private:
    virtual void DoSomething() { ... }
};

Bu nedenle, FooUser sınıfı, FooInterface arabirimi aracılığıyla FooImplementer özel yöntemlerini çağırabilirken, diğer harici sınıflar bunu yapamaz. Bu, arabirimler olarak tanımlanan belirli geri aramaları işlemek için harika bir modeldir.


1
Aslında, özel miras, özel IS-A'dır.
curiousguy

18

Bence C ++ FAQ Lite'daki kritik bölüm :

Özel miras için meşru, uzun vadeli bir kullanım, bir Wilma sınıfında kod kullanan bir Fred sınıfı oluşturmak istediğinizde ve Wilma sınıfından gelen kodun, yeni sınıfınız Fred'in üye işlevlerini çağırması gerektiğidir. Bu durumda, Fred, Wilma'daki sanal olmayanları çağırır ve Wilma, Fred tarafından geçersiz kılınan kendi içinde (genellikle saf sanallar) çağırır. Bunu kompozisyonla yapmak çok daha zor.

Şüpheniz varsa, özel miras yerine kompozisyonu tercih etmelisiniz.


4

Diğer kodun arayüze dokunmasını istemediğim yerlerde (yalnızca miras alan sınıf) miras aldığım arayüzler (yani soyut sınıflar) için yararlı buluyorum.

[bir örnekte düzenlenmiştir]

Yukarıda bağlantılı örneği ele alalım . Bunu söylüyorum

[...] sınıf Wilma, yeni sınıfınız Fred'in üye işlevlerini çağırmalıdır.

Wilma'nın, Fred'in belirli üye işlevlerini çağırabilmesini istediğini söylemek, ya da daha ziyade Wilma'nın bir arayüz olduğunu söylemektir . Dolayısıyla, örnekte belirtildiği gibi

özel miras kötü değildir; Birisinin kodunuzu kıracak bir şeyi değiştirme olasılığını artırdığı için bakımı daha pahalıdır.

arayüz gereksinimlerimizi karşılamaya ihtiyaç duyan veya kodu kıran programcıların istenen etkisine ilişkin yorumlar. Ve, fredCallsWilma () korumalı olduğundan, sadece arkadaşlar ve türetilmiş sınıflar ona dokunabilir, yani sadece miras alan sınıfın (ve arkadaşların) dokunabileceği miras alınmış bir arayüz (soyut sınıf).

[başka bir örnekte düzenlenmiştir]

Bu sayfada özel arayüzler kısaca tartışılmaktadır (başka bir açıdan).


Kulağa pek kullanışlı gelmiyor ... bir örnek gönderebilir misin

Sanırım nereye gittiğinizi anlıyorum ... Tipik bir kullanım durumu, Wilma'nın Fred'de sanal fonksiyonları çağırması gereken bir tür yardımcı sınıf olması olabilir, ancak diğer sınıfların Fred'in şartlara göre uygulandığını bilmesine gerek yoktur. Wilma. Sağ?
j_random_hacker

Evet. Anladığım kadarıyla 'arayüz' teriminin Java'da daha yaygın olarak kullanıldığını belirtmeliyim. İlk duyduğumda daha iyi bir isim verilebileceğini düşündüm. Çünkü bu örnekte, normalde kelime hakkında düşündüğümüz şekilde kimsenin arayüz oluşturmadığı bir arayüzümüz var.
önyargı

@Hayır: Evet, "Wilma bir arayüzdür" ifadenizin biraz belirsiz olduğunu düşünüyorum, çünkü çoğu insan bunu Wilma'nın sadece Wilma ile bir sözleşme değil , Fred'in dünyaya tedarik etmeyi planladığı bir arayüz olduğu anlamına gelecektir .
j_random_hacker

@j_ Bu yüzden arayüzün kötü bir isim olduğunu düşünüyorum. Arayüz terimi, birinin düşündüğü gibi dünya için bir anlamı olması gerekmez , aksine bir işlevsellik garantisidir. Aslında, Program Tasarımı dersimde arayüz terimi konusunda tartışmalıydım. Ancak, bize verileni kullanıyoruz ...
önyargı

2

Bazen, başka bir arabirimin arabiriminde daha küçük bir arabirim (örneğin bir koleksiyon) göstermek istediğimde özel kalıtımı kullanmayı yararlı buluyorum; Java.

class BigClass;

struct SomeCollection
{
    iterator begin();
    iterator end();
};

class BigClass : private SomeCollection
{
    friend struct SomeCollection;
    SomeCollection &GetThings() { return *this; }
};

Sonra SomeCollection'ın BigClass'a erişmesi gerekiyorsa, bunu yapabilir static_cast<BigClass *>(this). Fazladan bir veri üyesinin yer kaplamasına gerek yok.


BigClassBu örnekte var olduğuna dair ileri beyana gerek yok mu? Bunu ilginç buluyorum, ama yüzüme haince çığlık atıyor.
Thomas Eding

2

Sınırlı bir kullanımı olmasına rağmen özel miras için güzel bir uygulama buldum.

Çözülmesi gereken sorun

Aşağıdaki C API'sinin size verildiğini varsayalım:

#ifdef __cplusplus
extern "C" {
#endif

    typedef struct
    {
        /* raw owning pointer, it's C after all */
        char const * name;

        /* more variables that need resources
         * ...
         */
    } Widget;

    Widget const * loadWidget();

    void freeWidget(Widget const * widget);

#ifdef __cplusplus
} // end of extern "C"
#endif

Şimdi işiniz bu API'yi C ++ kullanarak uygulamak.

C-ish yaklaşım

Elbette şu şekilde bir C-ish uygulama stili seçebiliriz:

Widget const * loadWidget()
{
    auto result = std::make_unique<Widget>();
    result->name = strdup("The Widget name");
    // More similar assignments here
    return result.release();
}

void freeWidget(Widget const * const widget)
{
    free(result->name);
    // More similar manual freeing of resources
    delete widget;
}

Ancak birkaç dezavantaj var:

  • Manuel kaynak (ör. Bellek) yönetimi
  • structYanlış ayarlamak çok kolay
  • Serbest bırakırken kaynakları serbest bırakmayı unutmak kolaydır. struct
  • C-imsi

C ++ Yaklaşımı

C ++ kullanmamıza izin verildi, öyleyse neden tüm güçlerini kullanmayalım?

Otomatik kaynak yönetimine giriş

Yukarıdaki sorunların tümü temelde manuel kaynak yönetimine bağlıdır. Akla gelen çözüm, her değişken için Widgettüretilmiş sınıfa bir kaynak yönetimi örneğini miras almak ve buna eklemektir WidgetImpl:

class WidgetImpl : public Widget
{
public:
    // Added bonus, Widget's members get default initialized
    WidgetImpl()
        : Widget()
    {}

    void setName(std::string newName)
    {
        m_nameResource = std::move(newName);
        name = m_nameResource.c_str();
    }

    // More similar setters to follow

private:
    std::string m_nameResource;
};

Bu, uygulamayı şu şekilde basitleştirir:

Widget const * loadWidget()
{
    auto result = std::make_unique<WidgetImpl>();
    result->setName("The Widget name");
    // More similar setters here
    return result.release();
}

void freeWidget(Widget const * const widget)
{
    // No virtual destructor in the base class, thus static_cast must be used
    delete static_cast<WidgetImpl const *>(widget);
}

Bunun gibi yukarıdaki tüm sorunları çözdük. Ancak bir müşteri, kurucuları unutabilir WidgetImplve Widgetüyelere doğrudan atayabilir .

Özel miras sahneye çıkıyor

WidgetÜyeleri kapsüllemek için özel miras kullanıyoruz. Ne yazık ki artık iki sınıf arasında dönüşüm yapmak için iki ekstra işleve ihtiyacımız var:

class WidgetImpl : private Widget
{
public:
    WidgetImpl()
        : Widget()
    {}

    void setName(std::string newName)
    {
        m_nameResource = std::move(newName);
        name = m_nameResource.c_str();
    }

    // More similar setters to follow

    Widget const * toWidget() const
    {
        return static_cast<Widget const *>(this);
    }

    static void deleteWidget(Widget const * const widget)
    {
        delete static_cast<WidgetImpl const *>(widget);
    }

private:
    std::string m_nameResource;
};

Bu, aşağıdaki uyarlamaları gerekli kılar:

Widget const * loadWidget()
{
    auto widgetImpl = std::make_unique<WidgetImpl>();
    widgetImpl->setName("The Widget name");
    // More similar setters here
    auto const result = widgetImpl->toWidget();
    widgetImpl.release();
    return result;
}

void freeWidget(Widget const * const widget)
{
    WidgetImpl::deleteWidget(widget);
}

Bu çözüm tüm sorunları çözer. Manuel bellek yönetimi yoktur ve Widgetgüzel bir şekilde kapsüllenmiştir, böyleceWidgetImpl artık herhangi bir genel veri üyesi yok. Uygulamanın doğru kullanılmasını kolaylaştırır, yanlış kullanımını zorlaştırır (imkansız?).

Kod parçacıkları Coliru'da bir derleme örneği oluşturur .


1

Türetilmiş sınıf - kodu yeniden kullanması gerekiyorsa ve - temel sınıfı değiştiremezsiniz ve - temelin üyelerini bir kilit altında kullanarak yöntemlerini koruyorsa.

o zaman özel kalıtımı kullanmalısınız, aksi takdirde bu türetilmiş sınıf aracılığıyla dışa aktarılan kilidi açılmış temel yöntemler tehlikesi yaşarsınız.


1

Bazen , örneğin toplama istiyorsanız, ancak toplanabilir varlığın değişen davranışıyla (sanal işlevleri geçersiz kılarak), toplamaya alternatif olabilir .

Ama haklısın, gerçek dünyadan pek fazla örneği yok.


0

Özel Miras, ilişki "bir" olmadığında kullanılacak, Ancak Yeni sınıf "var olan sınıf açısından uygulanabilir" veya yeni sınıf "mevcut sınıf gibi" çalışabilir.

"Andrei Alexandrescu, Herb Sutter'ın C ++ kodlama standartlarından" örnek: - Kare ve Dikdörtgen gibi iki sınıfın her birinin yükseklik ve genişliklerini ayarlamak için sanal işlevlere sahip olduğunu düşünün. O zaman Square, Dikdörtgenden doğru bir şekilde miras alamaz, çünkü değiştirilebilir bir Dikdörtgen kullanan kod SetWidth'in yüksekliği değiştirmediğini varsayar (Rectangle açıkça daralır veya daralmaz), oysa Square :: SetWidth bu sözleşmeyi ve kendi karesel değişmezliğini koruyamaz Aynı zaman. Ancak Dikdörtgen, Square istemcileri, örneğin bir Square'in alanının genişliğinin karesi olduğunu varsayarsa veya Dikdörtgenler için geçerli olmayan başka bir özelliğe güvenirlerse, Square'den de doğru bir şekilde miras alamaz.

Bir kare "is-a" dikdörtgen (matematiksel olarak), ancak Kare bir Dikdörtgen değildir (davranışsal olarak). Sonuç olarak, açıklamayı yanlış anlaşılmaya daha az eğilimli hale getirmek için "is-a" yerine "çalışır-a-gibi" (veya tercih ederseniz, "a olarak kullanılabilir") demeyi tercih ederiz.


0

Bir sınıf bir değişmez içerir. Değişmez, kurucu tarafından belirlenir. Bununla birlikte, birçok durumda nesnenin temsil durumunun bir görünümüne sahip olmak yararlıdır (bunu ağ üzerinden iletebilir veya bir dosyaya kaydedebilirsiniz - isterseniz DTO). REST, bir AggregateType açısından en iyi şekilde yapılır. Bu, özellikle haklıysanız geçerlidir. Düşünmek:

struct QuadraticEquationState {
   const double a;
   const double b;
   const double c;

   // named ctors so aggregate construction is available,
   // which is the default usage pattern
   // add your favourite ctors - throwing, try, cps
   static QuadraticEquationState read(std::istream& is);
   static std::optional<QuadraticEquationState> try_read(std::istream& is);

   template<typename Then, typename Else>
   static std::common_type<
             decltype(std::declval<Then>()(std::declval<QuadraticEquationState>()),
             decltype(std::declval<Else>()())>::type // this is just then(qes) or els(qes)
   if_read(std::istream& is, Then then, Else els);
};

// this works with QuadraticEquation as well by default
std::ostream& operator<<(std::ostream& os, const QuadraticEquationState& qes);

// no operator>> as we're const correct.
// we _might_ (not necessarily want) operator>> for optional<qes>
std::istream& operator>>(std::istream& is, std::optional<QuadraticEquationState>);

struct QuadraticEquationCache {
   mutable std::optional<double> determinant_cache;
   mutable std::optional<double> x1_cache;
   mutable std::optional<double> x2_cache;
   mutable std::optional<double> sum_of_x12_cache;
};

class QuadraticEquation : public QuadraticEquationState, // private if base is non-const
                          private QuadraticEquationCache {
public:
   QuadraticEquation(QuadraticEquationState); // in general, might throw
   QuadraticEquation(const double a, const double b, const double c);
   QuadraticEquation(const std::string& str);
   QuadraticEquation(const ExpressionTree& str); // might throw
}

Bu noktada, önbellek koleksiyonlarını konteynırlarda depolayabilir ve inşaata bakabilirsiniz. Gerçek bir işlem varsa kullanışlı. Önbelleğin QE'nin bir parçası olduğuna dikkat edin: QE'de tanımlanan işlemler, önbelleğin kısmen yeniden kullanılabilir olduğu anlamına gelebilir (örneğin, c toplamı etkilemez); yine de, önbellek olmadığında, bakmaya değer.

Özel kalıtım hemen hemen her zaman bir üye tarafından modellenebilir (gerekirse tabana referans depolanır). Bu şekilde modellemeye her zaman değmez; bazen miras, en etkili temsildir.


0

std::ostreamBazı küçük değişikliklere ihtiyacınız varsa ( bu soruda olduğu gibi ) yapmanız gerekebilir

  1. Sınıf oluşturun MyStreambuftüretilmiştirstd::streambufOradaki değişiklikleri ve uygulayan
  2. Bundan MyOStreamtüretilen bir sınıf oluşturun std::ostream, ayrıca bir örneğini başlatır ve yönetir MyStreambufve işaretçiyi bu örneğe yapıcısına iletir.std::ostream

İlk fikir, MyStreamörneği MyOStreamsınıfa bir veri üyesi olarak eklemek olabilir :

class MyOStream : public std::ostream
{
public:
    MyOStream()
        : std::basic_ostream{ &m_buf }
        , m_buf{}
    {}

private:
    MyStreambuf m_buf;
};

Ancak temel sınıflar herhangi bir veri üyesinden önce oluşturulur, bu nedenle henüz yapılandırılmamış bir std::streambuförneğe bir işaretçiyistd::ostream bu nedenle tanımlanmamış davranış olan geçirirsiniz.

Çözüm, Ben'in yukarıda belirtilen soruya verdiği yanıtta önerilmiştir , basitçe önce akış tamponundan, sonra akıştan devralın ve ardından akışı şu şekilde başlatın this:

class MyOStream : public MyStreamBuf, public std::ostream
{
public:
    MyOStream()
        : MyStreamBuf{}
        , basic_ostream{ this }
    {}
};

Bununla birlikte, ortaya çıkan sınıf std::streambuf, genellikle istenmeyen bir örnek olarak da kullanılabilir . Özel mirasa geçmek bu sorunu çözer:

class MyOStream : private MyStreamBuf, public std::ostream
{
public:
    MyOStream()
        : MyStreamBuf{}
        , basic_ostream{ this }
    {}
};

-1

Sırf C ++ 'nın bir özelliği olması, yararlı olduğu veya kullanılması gerektiği anlamına gelmez.

Hiç kullanmamalısın derim.

Yine de kullanıyorsanız, temelde kapsüllemeyi ihlal ediyorsunuz ve uyumu düşürüyorsunuz. Bir sınıfa veri koyuyorsunuz ve başka bir sınıfa verileri işleyen yöntemler ekliyorsunuz.

Diğer C ++ özellikleri gibi, bir sınıfı mühürlemek gibi yan etkiler elde etmek için kullanılabilir (dribeas'ın cevabında belirtildiği gibi), ancak bu onu iyi bir özellik yapmaz.


alay mı ediyorsun tüm sahip olduğum bir -1! her neyse, -100 oy
alsa

9
" Temelde kapsüllemeyi ihlal ediyorsunuz" Bir örnek verebilir misiniz?
curiousguy

1
Bir sınıftaki veriler ve diğerindeki davranış, esneklikte bir artışa benziyor, çünkü birden fazla davranış sınıfı ve müşteri olabilir ve istediklerini tatmin etmek için hangisine ihtiyaç duyduklarını seçebilirler
makar
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.