Kapan saya harus menggunakan warisan pribadi C ++?


116

Tidak seperti warisan yang dilindungi, warisan pribadi C ++ masuk ke dalam pengembangan C ++ arus utama. Namun, saya masih belum menemukan manfaat yang baik untuk itu.

Kapan kalian menggunakannya?

c++  oop 

Jawaban:


60

Catatan setelah penerimaan jawaban: Ini BUKAN jawaban lengkap. Bacalah jawaban lain seperti di sini (secara konseptual) dan di sini (baik teoretis maupun praktis) jika Anda tertarik dengan pertanyaannya. Ini hanyalah trik mewah yang dapat dicapai dengan warisan pribadi. Meskipun mewah , ini bukanlah jawaban atas pertanyaan tersebut.

Selain penggunaan dasar dari warisan pribadi yang ditunjukkan di C ++ FAQ (ditautkan di komentar orang lain), Anda dapat menggunakan kombinasi warisan pribadi dan virtual untuk menyegel kelas (dalam terminologi .NET) atau untuk membuat kelas final (dalam terminologi Java) . Ini bukan penggunaan umum, tapi bagaimanapun juga menurut saya ini menarik:

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

Sealed dapat dipakai. Ini berasal dari ClassSealer dan dapat memanggil konstruktor pribadi secara langsung karena ini adalah teman.

FailsToDerive tidak akan dikompilasi karena harus memanggil konstruktor ClassSealer secara langsung (persyaratan warisan virtual), tetapi tidak bisa karena bersifat pribadi di kelas Sealed dan dalam hal ini FailsToDerive bukan teman ClassSealer .


EDIT

Disebutkan dalam komentar bahwa ini tidak dapat dibuat generik pada saat menggunakan CRTP. Standar C ++ 11 menghilangkan batasan itu dengan menyediakan sintaks yang berbeda untuk berteman dengan argumen template:

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

Tentu saja ini semua bisa diperdebatkan, karena C ++ 11 menyediakan finalkata kunci kontekstual untuk tujuan ini:

class Sealed final // ...

Itu teknik yang bagus. Saya akan menulis entri blog di atasnya.

1
Pertanyaan: jika kita tidak menggunakan warisan virtual, maka FailsToDerive akan dikompilasi. Benar?

4
+1. @Sasha: Benar, pewarisan virtual diperlukan karena kelas yang paling diturunkan selalu memanggil konstruktor dari semua kelas yang diwarisi secara virtual secara langsung, yang tidak terjadi pada pewarisan biasa.
j_random_hacker

5
Ini dapat dibuat umum, tanpa membuat ClassSealer khusus untuk setiap kelas yang ingin Anda segel! Lihat ini: class ClassSealer {protected: ClassSealer () {}}; itu saja.

+1 Iraimbilanja, sangat keren! BTW Saya melihat komentar Anda sebelumnya (sekarang dihapus) tentang menggunakan CRTP: Saya pikir itu seharusnya berfungsi, hanya sulit untuk mendapatkan sintaks untuk teman template dengan benar. Tetapi bagaimanapun juga, solusi non-template Anda jauh lebih hebat :)
j_random_hacker

138

Saya menggunakannya sepanjang waktu. Beberapa contoh di luar kepala saya:

  • Ketika saya ingin mengekspos beberapa tetapi tidak semua antarmuka kelas dasar. Warisan publik akan menjadi kebohongan, karena substitusi Liskov rusak, sedangkan komposisi berarti menulis banyak fungsi penerusan.
  • Ketika saya ingin mendapatkan dari kelas beton tanpa penghancur virtual. Warisan publik akan mengundang klien untuk menghapus melalui pointer-to-base, memicu perilaku tidak terdefinisi.

Contoh tipikal diturunkan secara pribadi dari wadah STL:

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...  
};
  • Saat mengimplementasikan Pola Adaptor, mewarisi secara pribadi dari kelas Adapted menghemat keharusan untuk meneruskan ke instans tertutup.
  • Untuk mengimplementasikan antarmuka pribadi. Ini sering muncul dengan Pola Pengamat. Biasanya kelas Observer saya, MyClass mengatakan, berlangganan sendiri dengan beberapa Subjek. Kemudian, hanya MyClass yang perlu melakukan konversi MyClass -> Observer. Sistem lainnya tidak perlu mengetahuinya, jadi warisan pribadi ditunjukkan.

4
@Krsna: Sebenarnya, menurut saya tidak. Hanya ada satu alasan di sini: kemalasan, selain yang terakhir, yang akan lebih sulit untuk diselesaikan.
Matthieu M.

11
Tidak terlalu banyak kemalasan (kecuali Anda bersungguh-sungguh). Ini memungkinkan pembuatan overload baru dari fungsi yang telah diekspos tanpa pekerjaan tambahan. Jika di C ++ 1x mereka menambahkan 3 overload baru push_back, MyVectordapatkan secara gratis.
David Stone

@DavidStone, tidak bisakah Anda melakukannya dengan metode template?
Julien__

5
@Julien__: Ya, Anda bisa menulis 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)...); }atau menulis menggunakan Base::f;. Jika Anda menginginkan sebagian besar fungsionalitas dan fleksibilitas yang diberikan oleh private inheritance dan sebuah usingpernyataan, Anda memiliki monster itu untuk setiap fungsi (dan jangan lupa tentang constdan volatilekelebihan beban!).
David Stone

2
Saya mengatakan sebagian besar fungsionalitas karena Anda masih meminta satu konstruktor langkah tambahan yang tidak ada dalam versi pernyataan menggunakan. Secara umum, Anda akan mengharapkan ini untuk dioptimalkan, tetapi fungsinya secara teoritis dapat mengembalikan tipe yang tidak dapat dipindahkan berdasarkan nilai. Template fungsi penerusan juga memiliki contoh template tambahan dan kedalaman konsteks. Ini dapat menyebabkan program Anda mengalami batasan implementasi.
David Stone

31

Penggunaan kanonik warisan pribadi adalah hubungan "diimplementasikan dalam" (terima kasih kepada Scott Meyers 'C ++ Efektif' untuk kata-kata ini). Dengan kata lain, antarmuka eksternal dari kelas yang mewarisi tidak memiliki hubungan (terlihat) dengan kelas yang diwarisi, tetapi ia menggunakannya secara internal untuk mengimplementasikan fungsinya.


6
Mungkin ada baiknya menyebutkan salah satu alasan mengapa ini digunakan dalam kasus ini: Ini memungkinkan pengoptimalan kelas dasar kosong dilakukan, yang tidak akan terjadi jika kelas telah menjadi anggota, bukan kelas dasar.
jalf

2
penggunaan utamanya adalah untuk mengurangi konsumsi ruang di tempat yang benar-benar penting, misalnya dalam kelas string yang dikontrol kebijakan atau dalam pasangan terkompresi. sebenarnya, boost :: compressed_pair menggunakan inheritance yang dilindungi.
Johannes Schaub - litb

jalf: Hei, aku tidak menyadarinya. Saya pikir warisan nonpublik terutama digunakan sebagai peretasan ketika Anda memerlukan akses ke anggota kelas yang dilindungi. Saya bertanya-tanya mengapa objek kosong akan menempati ruang apa pun saat menggunakan komposisi. Mungkin untuk alamat universal ...

3
Ini juga berguna untuk membuat kelas tidak dapat disalin - cukup mewarisi secara pribadi dari kelas kosong yang tidak dapat disalin. Sekarang Anda tidak harus melalui pekerjaan sibuk mendeklarasikan tetapi tidak mendefinisikan operator penugasan & konstruktor salinan pribadi. Meyers juga membicarakan hal ini.
Michael Burr

Saya tidak menyadari bahwa pertanyaan ini sebenarnya adalah tentang warisan pribadi dan bukan tentang warisan yang dilindungi. ya saya kira ada cukup banyak aplikasi untuk itu. tidak bisa memikirkan banyak contoh untuk warisan yang dilindungi: / sepertinya itu jarang berguna.
Johannes Schaub - litb

23

Salah satu kegunaan private inheritence adalah ketika Anda memiliki kelas yang mengimplementasikan antarmuka, yang kemudian didaftarkan dengan beberapa objek lain. Anda membuat antarmuka itu menjadi pribadi sehingga kelas itu sendiri harus mendaftar dan hanya objek tertentu yang didaftarkannya yang dapat menggunakan fungsi tersebut.

Sebagai contoh:

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() { ... }
};

Oleh karena itu, kelas FooUser dapat memanggil metode pribadi FooImplementer melalui antarmuka FooInterface, sedangkan kelas eksternal lainnya tidak bisa. Ini adalah pola yang bagus untuk menangani callback tertentu yang didefinisikan sebagai antarmuka.


1
Memang, warisan pribadi adalah IS-A pribadi.
penasaran

18

Saya pikir bagian penting dari C ++ FAQ Lite adalah:

Penggunaan jangka panjang yang sah untuk warisan pribadi adalah saat Anda ingin membangun kelas Fred yang menggunakan kode di kelas Wilma, dan kode dari kelas Wilma perlu memanggil fungsi anggota dari kelas baru Anda, Fred. Dalam kasus ini, Fred memanggil non-virtual di Wilma, dan Wilma memanggil (biasanya virtual murni) dengan sendirinya, yang diganti oleh Fred. Ini akan lebih sulit dilakukan dengan komposisi.

Jika ragu, Anda sebaiknya memilih komposisi daripada warisan pribadi.


4

Saya merasa berguna untuk antarmuka (yaitu kelas abstrak) yang saya warisi di mana saya tidak ingin kode lain menyentuh antarmuka (hanya kelas yang mewarisi).

[diedit dalam contoh]

Ambil contoh yang ditautkan di atas. Mengatakan itu

[...] kelas Wilma perlu memanggil fungsi anggota dari kelas baru Anda, Fred.

adalah mengatakan bahwa Wilma meminta Fred untuk dapat menjalankan fungsi anggota tertentu, atau, lebih tepatnya mengatakan bahwa Wilma adalah sebuah antarmuka . Karenanya, seperti yang disebutkan dalam contoh

warisan pribadi tidak jahat; itu hanya lebih mahal untuk dipelihara, karena meningkatkan kemungkinan seseorang akan mengubah sesuatu yang akan merusak kode Anda.

komentar tentang efek yang diinginkan dari pemrogram yang harus memenuhi persyaratan antarmuka kami, atau memecahkan kode. Dan, karena fredCallsWilma () dilindungi hanya teman dan kelas turunan yang dapat menyentuhnya, yaitu antarmuka yang diwariskan (kelas abstrak) yang hanya dapat disentuh oleh kelas yang mewarisi (dan teman).

[diedit di contoh lain]

Halaman ini secara singkat membahas antarmuka pribadi (dari sudut lain).


Kedengarannya tidak terlalu berguna ... dapatkah Anda memposting contoh

Saya rasa saya mengerti ke mana Anda akan pergi ... Kasus penggunaan yang khas mungkin bahwa Wilma adalah sejenis kelas utilitas yang perlu memanggil fungsi virtual di Fred, tetapi kelas lain tidak perlu tahu bahwa Fred diimplementasikan-dalam-istilah- dari Wilma. Baik?
j_random_hacker

Iya. Saya harus menunjukkan bahwa, menurut pemahaman saya, istilah 'antarmuka' lebih umum digunakan di Java. Ketika saya pertama kali mendengarnya, saya pikir itu bisa diberi nama yang lebih baik. Karena, dalam contoh ini kita memiliki antarmuka yang tidak ada yang berinteraksi dengan cara yang biasanya kita pikirkan tentang kata tersebut.
bias

@Noos: Ya, saya pikir pernyataan Anda "Wilma adalah antarmuka" agak ambigu, karena kebanyakan orang akan menganggap ini berarti bahwa Wilma adalah antarmuka yang ingin dipasok Fred ke dunia , bukan hanya kontrak dengan Wilma.
j_random_hacker

@j_ Itulah mengapa menurut saya antarmuka adalah nama yang buruk. Antarmuka, istilahnya, tidak perlu berarti bagi dunia seperti yang orang pikirkan, tetapi lebih merupakan jaminan fungsionalitas. Sebenarnya, saya sempat bertengkar tentang istilah antarmuka di kelas Desain Program saya. Tapi, kami menggunakan apa yang kami berikan ...
bias

2

Kadang-kadang saya merasa berguna untuk menggunakan private inheritance ketika saya ingin mengekspos antarmuka yang lebih kecil (misalnya koleksi) di antarmuka lain, di mana implementasi collection memerlukan akses ke status kelas yang mengekspos, dengan cara yang mirip dengan kelas dalam di Jawa.

class BigClass;

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

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

Kemudian jika SomeCollection perlu mengakses BigClass, itu bisa static_cast<BigClass *>(this). Tidak perlu ada anggota data tambahan yang menghabiskan ruang.


Tidak perlu deklarasi ke depan BigClassapakah ada dalam contoh ini? Menurut saya ini menarik, tetapi wajah saya menjerit keras.
Thomas Eding

2

Saya menemukan aplikasi bagus untuk warisan pribadi, meskipun penggunaannya terbatas.

Masalah untuk dipecahkan

Misalkan Anda diberi C API berikut:

#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

Sekarang tugas Anda adalah mengimplementasikan API ini menggunakan C ++.

Pendekatan C-ish

Tentu saja kita bisa memilih gaya implementasi C-ish seperti ini:

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;
}

Tetapi ada beberapa kelemahan:

  • Manajemen sumber daya manual (misalnya memori)
  • Mudah untuk mengatur structkesalahan
  • Sangat mudah untuk melupakan saat membebaskan sumber daya saat membebaskan struct
  • Ini adalah C-ish

Pendekatan C ++

Kami diizinkan menggunakan C ++, jadi mengapa tidak menggunakan kekuatan penuhnya?

Memperkenalkan manajemen sumber daya otomatis

Masalah di atas pada dasarnya semua terkait dengan manajemen sumber daya manual. Solusi yang terlintas dalam pikiran adalah mewarisi dari Widgetdan menambahkan instance pengelolaan sumber daya ke kelas turunan WidgetImpluntuk setiap variabel:

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;
};

Ini menyederhanakan implementasi sebagai berikut:

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);
}

Seperti ini kami memperbaiki semua masalah di atas. Namun klien masih bisa melupakan penyetel WidgetImpldan menetapkan keWidget anggota secara langsung.

Warisan pribadi memasuki panggung

Untuk merangkum Widgetanggota kami menggunakan warisan pribadi. Sayangnya kita sekarang membutuhkan dua fungsi tambahan untuk digunakan di antara kedua kelas:

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;
};

Ini membuat adaptasi berikut diperlukan:

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);
}

Solusi ini menyelesaikan semua masalah. Tidak ada manajemen memori manual dan Widgetdikemas dengan baik sehingga WidgetImpltidak memiliki anggota data publik lagi. Itu membuat implementasi mudah digunakan dengan benar dan sulit (tidak mungkin?) Untuk digunakan salah.

Potongan kode membentuk contoh kompilasi di Coliru .


1

Jika kelas turunan - perlu menggunakan kembali kode dan - Anda tidak dapat mengubah kelas dasar dan - melindungi metodenya menggunakan anggota basis di bawah kunci.

maka Anda harus menggunakan warisan pribadi, jika tidak, Anda akan menghadapi bahaya metode dasar tidak terkunci yang diekspor melalui kelas turunan ini.


1

Terkadang ini bisa menjadi alternatif untuk agregasi , misalnya jika Anda menginginkan agregasi tetapi dengan perilaku entitas agregat yang diubah (menimpa fungsi virtual).

Tapi Anda benar, tidak banyak contoh dari dunia nyata.


0

Private Inheritance untuk digunakan jika relasi bukan "adalah", tetapi kelas baru dapat "diimplementasikan dalam istilah kelas yang sudah ada" atau kelas baru "berfungsi seperti" kelas yang sudah ada.

Contoh dari "standar pengkodean C ++ oleh Andrei Alexandrescu, Herb Sutter": - Pertimbangkan bahwa dua kelas Square dan Rectangle masing-masing memiliki fungsi virtual untuk mengatur tinggi dan lebarnya. Maka Square tidak dapat mewarisi dengan benar dari Rectangle, karena kode yang menggunakan Rectangle yang dapat dimodifikasi akan mengasumsikan bahwa SetWidth tidak mengubah ketinggian (apakah Rectangle secara eksplisit mendokumentasikan kontrak itu atau tidak), sedangkan Square :: SetWidth tidak dapat mempertahankan kontrak itu dan invarian kuadratnya sendiri di waktu yang sama. Tetapi Persegi juga tidak dapat mewarisi dengan benar dari Persegi, jika klien Persegi mengasumsikan misalnya bahwa luas persegi adalah lebar kuadratnya, atau jika mereka bergantung pada beberapa properti lain yang tidak berlaku untuk Persegi.

Persegi "adalah-a" persegi panjang (secara matematis) tetapi Persegi bukanlah Persegi (secara perilaku). Akibatnya, daripada "is-a", kami lebih memilih untuk mengatakan "works-like-a" (atau, jika Anda lebih suka, "usable-as-a") untuk membuat deskripsi tidak terlalu rentan terhadap kesalahpahaman.


0

Kelas memiliki invarian. Invarian ditetapkan oleh konstruktor. Namun, dalam banyak situasi, akan berguna untuk memiliki pandangan tentang status representasi objek (yang dapat Anda kirimkan melalui jaringan atau simpan ke file - DTO jika Anda mau). REST paling baik dilakukan dalam hal AggregateType. Ini terutama benar jika Anda benar konst. Mempertimbangkan:

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
}

Pada titik ini, Anda mungkin hanya menyimpan koleksi cache dalam container dan mencarinya saat konstruksi. Berguna jika ada pemrosesan nyata. Perhatikan bahwa cache adalah bagian dari QE: operasi yang didefinisikan pada QE mungkin berarti cache sebagian dapat digunakan kembali (misalnya, c tidak mempengaruhi jumlah); namun, jika tidak ada cache, ada baiknya untuk mencarinya.

Warisan pribadi hampir selalu dapat dimodelkan oleh anggota (menyimpan referensi ke pangkalan jika diperlukan). Tidak selalu layak untuk membuat model seperti itu; terkadang warisan adalah representasi yang paling efisien.


0

Jika Anda membutuhkan std::ostreamdengan beberapa perubahan kecil (seperti dalam pertanyaan ini ) Anda mungkin perlu

  1. Buat kelas MyStreambufyang berasal daristd::streambuf dan terapkan perubahan di sana
  2. Buat kelas MyOStreamyang diturunkan dari std::ostreamitu juga menginisialisasi dan mengelola sebuah instance MyStreambufdan meneruskan pointer ke instance itu ke konstruktorstd::ostream

Ide pertama mungkin untuk menambahkan MyStreaminstance sebagai anggota data ke MyOStreamkelas:

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

private:
    MyStreambuf m_buf;
};

Tapi kelas dasar dibangun sebelum anggota data apa pun sehingga Anda meneruskan pointer ke std::streambufinstance yang belum dibangunstd::ostream yang yang merupakan perilaku tidak terdefinisi.

Solusinya diusulkan dalam jawaban Ben atas pertanyaan yang disebutkan di atas , cukup mewarisi dari buffer aliran terlebih dahulu, kemudian dari aliran dan kemudian menginisialisasi aliran dengan this:

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

Namun kelas yang dihasilkan juga bisa digunakan sebagai std::streambufinstance yang biasanya tidak diinginkan. Beralih ke warisan pribadi memecahkan masalah ini:

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

-1

Hanya karena C ++ memiliki fitur, bukan berarti itu berguna atau harus digunakan.

Saya akan mengatakan Anda tidak boleh menggunakannya sama sekali.

Jika Anda tetap menggunakannya, pada dasarnya Anda melanggar enkapsulasi, dan menurunkan kohesi. Anda meletakkan data di satu kelas, dan menambahkan metode yang memanipulasi data di kelas lain.

Seperti fitur C ++ lainnya, ini dapat digunakan untuk mencapai efek samping seperti menyegel kelas (seperti yang disebutkan dalam jawaban dribeas), tetapi ini tidak menjadikannya fitur yang bagus.


apakah kamu sedang menyindir? yang saya miliki adalah -1! bagaimanapun saya tidak akan menghapus ini meskipun mendapat -100 suara
hasen

9
" Anda pada dasarnya melanggar enkapsulasi " Dapatkah Anda memberikan contoh?
penasaran

1
data di satu kelas dan perilaku di kelas lain terdengar seperti peningkatan fleksibilitas, karena bisa ada lebih dari satu kelas perilaku dan klien dan memilih mana yang mereka butuhkan untuk memenuhi apa yang mereka inginkan
makar
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.