Mengapa basis-untuk-semua-objek tidak disarankan dalam C ++

76

Stroustrup berkata, "Jangan segera menciptakan basis yang unik untuk semua kelas Anda (kelas Object). Biasanya, Anda dapat melakukan yang lebih baik tanpa itu untuk banyak / sebagian besar kelas." (The C ++ Bahasa Pemrograman Edisi Keempat, Bagian 1.3.4)

Mengapa kelas-dasar-untuk-segalanya umumnya merupakan ide yang buruk, dan kapan masuk akal untuk membuatnya?

Matthew James Briggs
sumber
16
karena C ++ bukan Java ... Dan Anda seharusnya tidak mencoba memaksanya.
AK_
10
Ditanyakan pada Stack Overflow: Mengapa tidak ada kelas dasar di C ++?
26
Juga, saya tidak setuju dengan suara dekat untuk "terutama berdasarkan pendapat." Ada alasan yang sangat spesifik yang dapat dijelaskan untuk ini, karena jawaban membuktikan baik pada pertanyaan ini dan pertanyaan SO terkait.
2
Ini prinsip "Anda tidak akan membutuhkannya" gesit. Kecuali Anda telah mengidentifikasi kebutuhan khusus untuk itu, jangan lakukan itu (sampai Anda melakukannya).
Jool
3
@AK_: Ada "sebodoh" yang hilang dalam komentar Anda.
DeadMG

Jawaban:

75

Karena apa yang akan dimiliki objek itu untuk fungsionalitas? Di java semua kelas Base miliki adalah toString, kode hash & kesetaraan dan variabel kondisi + monitor.

  • ToString hanya berguna untuk debugging.

  • kode hash hanya berguna jika Anda ingin menyimpannya dalam koleksi berbasis hash (preferensi dalam C ++ adalah untuk melewatkan fungsi hashing ke wadah sebagai param template atau untuk menghindari std::unordered_*sama sekali dan sebagai gantinya menggunakan std::vectordan polos daftar unordered).

  • kesetaraan tanpa objek dasar dapat dibantu pada waktu kompilasi, jika mereka tidak memiliki tipe yang sama maka mereka tidak dapat sama. Dalam C ++ ini adalah kesalahan waktu kompilasi.

  • variabel monitor dan kondisi lebih baik dimasukkan secara eksplisit dalam kasus per kasus.

Namun ketika ada lebih banyak yang perlu dilakukan maka ada kasus penggunaan.

Sebagai contoh di QT ada QObjectkelas root yang membentuk dasar afinitas thread, hierarki kepemilikan orangtua-anak dan mekanisme slot sinyal. Ini juga memaksa digunakan oleh pointer untuk QObjects, Namun banyak Kelas di Qt tidak mewarisi QObject karena mereka tidak memerlukan slot sinyal (terutama tipe nilai dari beberapa deskripsi).

ratchet freak
sumber
7
Anda lupa menyebutkan mungkin alasan utama Java memiliki kelas dasar: Sebelum generik, kelas koleksi membutuhkan kelas dasar agar berfungsi. Semuanya (penyimpanan internal, parameter, nilai pengembalian) diketik Object.
Aleksandr Dubinsky
1
@AleksandrDubinsky: Dan obat generik hanya menambahkan gula sintaksis, tidak benar-benar mengubah apa pun selain polesan.
Deduplicator
4
Saya berpendapat, kode hash, kesetaraan dan dukungan monitor adalah kesalahan desain di Jawa juga. Siapa yang mengira itu adalah ide yang baik untuk membuat semua benda terkunci ?!
usr
1
Ya, tapi tidak ada yang menginginkannya. Kapan terakhir kali Anda perlu mengunci objek dan tidak bisa membuat instance objek kunci terpisah untuk melakukan itu. Sangat jarang dan itu membebani segalanya. Orang-orang Jawa memiliki pemahaman yang buruk tentang keselamatan benang saat itu sebagai bukti oleh semua benda menjadi kunci dan oleh koleksi aman benang yang sekarang sudah usang. Keamanan thread adalah properti global, bukan properti per objek.
usr
2
" HashCode hanya berguna jika Anda ingin menyimpannya dalam koleksi berbasis hash (preferensi dalam C ++ adalah untuk std :: vector dan daftar polos unordered). " Argumen nyata untuk _hashCodetidak 'menggunakan wadah yang berbeda' melainkan menunjuk keluar bahwa C ++ std::unordered_maptidak hashing menggunakan argumen templat, alih-alih membutuhkan kelas elemen itu sendiri untuk menyediakan implementasi. Yaitu, seperti semua wadah bagus lainnya & manajer sumber daya di C ++, ini tidak mengganggu; itu tidak mencemari semua objek dengan fungsi atau data kalau-kalau seseorang mungkin membutuhkannya dalam beberapa konteks nanti.
underscore_d
100

Karena tidak ada fungsi yang dibagikan oleh semua objek. Tidak ada yang bisa dimasukkan ke dalam antarmuka ini yang masuk akal untuk semua kelas.

DeadMG
sumber
10
+1 untuk kesederhanaan jawabannya, ini adalah satu-satunya alasan.
BWG
7
Dalam kerangka besar yang saya miliki pengalaman, kelas dasar umum menyediakan serialisasi dan infrastruktur refleksi yang diinginkan dalam konteks <wh Apapun>. Meh Itu hanya mengakibatkan orang membuat serial sekelompok cruft bersama dengan data dan metadata dan membuat format data terlalu besar dan rumit untuk menjadi efisien.
dmckee
19
@ Dckckee: Saya juga berpendapat bahwa serialisasi dan refleksi hampir tidak berguna secara universal.
DeadMG
16
@DeadMG: "TAPI APA JIKA ANDA PERLU MENYIMPAN SEGALA SESUATU?"
deworde
8
Saya tidak tahu, Anda menempelkannya dalam tanda kutip, Anda menggunakan semua topi, dan orang-orang tidak dapat melihat lelucon. @ MSalters: Ya, itu seharusnya mudah, ia memiliki jumlah minimal negara, Anda cukup menentukan bahwa itu ada. Saya dapat menulis nama saya di daftar tanpa memasukkan loop rekursif.
deworde
25

Setiap kali Anda membangun hierarki warisan benda yang tinggi, Anda cenderung mengalami masalah dengan Fragile Base Class (Wikipedia.) .

Memiliki banyak hierarki warisan kecil yang terpisah (berbeda, terisolasi) mengurangi peluang untuk mengalami masalah ini.

Menjadikan semua objek Anda bagian dari hierarki warisan tunggal praktis menjamin bahwa Anda akan mengalami masalah ini.

Mike Nakis
sumber
6
Ketika kelas dasar (di Jawa "java.lang.Object") tidak berisi metode apa pun yang memanggil metode lain masalah Kelas Basis Fragile tidak dapat terjadi.
Martin Rosenau
3
Kelas dasar yang sangat berguna!
Mike Nakis
9
@ MartinRosenau ... seperti yang dapat Anda lakukan di C ++ tanpa perlu kelas master base!
gbjbaanb
5
@ DavorŽdralo Jadi C ++ memiliki nama bodoh untuk fungsi dasar ("operator <<" alih-alih sesuatu yang masuk akal seperti "DebugPrint"), sementara Java memiliki keanehan dari kelas dasar untuk setiap kelas yang Anda tulis, tanpa pengecualian. Saya pikir saya lebih suka kutil C ++.
Sebastian Redl
4
@ DavorŽdralo: Nama fungsi tidak relevan. Gambar sintaks cout.print(x).print(0.5).print("Bye\n")- tidak bergantung operator<<.
MSalters
24

Karena:

  1. Anda tidak harus membayar untuk apa yang tidak Anda gunakan.
  2. Fungsi-fungsi ini kurang masuk akal dalam sistem tipe berbasis nilai daripada dalam sistem tipe berbasis referensi.

Menerapkan segala macam virtualfungsi memperkenalkan tabel virtual, yang membutuhkan overhead ruang per-objek yang tidak diperlukan atau diinginkan dalam banyak (kebanyakan?) Situasi.

Menerapkan secara toStringnonvirtual akan sangat tidak berguna, karena satu-satunya hal yang dapat dikembalikan adalah alamat objek, yang sangat tidak ramah pengguna, dan yang sudah diakses oleh penelepon, tidak seperti di Jawa.
Demikian pula, nonvirtual equalsatau hashCodehanya bisa menggunakan alamat untuk membandingkan objek, yang lagi-lagi sangat tidak berguna dan sering bahkan benar-benar salah - tidak seperti di Jawa, objek sering disalin dalam C ++, dan karenanya membedakan "identitas" suatu objek bahkan tidak selalu bermakna atau bermanfaat. (mis. seorang yang intbenar - benar tidak boleh memiliki identitas selain nilainya ... dua bilangan bulat dengan nilai yang sama harus sama.)

Mehrdad
sumber
Sehubungan dengan masalah ini dan masalah kelas dasar rapuh yang dicatat oleh Mike Nakis, perhatikan penelitian / proposal yang menarik untuk memperbaikinya di Jawa pada dasarnya dengan membuat semua metode secara internal (yaitu ketika dipanggil dari kelas yang sama) non-virtual tetapi menjaga perilaku virtual mereka saat secara eksternal disebut; untuk mendapatkan perilaku lama / standar (mis. virtual di mana-mana) proposal memperkenalkan openkata kunci baru . Saya tidak berpikir itu melampaui beberapa kertas sekalipun.
Fizz
Diskusi lebih lanjut tentang makalah itu dapat ditemukan di lambda-the-ultimate.org/classic/message12271.html
Fizz
Memiliki kelas dasar yang sama akan memungkinkan untuk menguji apa saja shared_ptr<Foo> untuk melihat apakah itu juga shared_ptr<Bar>(atau juga dengan jenis pointer lainnya), bahkan jika Foodan Barmerupakan kelas yang tidak terkait yang tidak saling mengenal satu sama lain. Membutuhkan hal seperti itu bekerja dengan "pointer mentah", mengingat sejarah bagaimana hal-hal seperti itu digunakan, akan mahal, tetapi untuk hal-hal yang akan disimpan tumpukan pula, biaya tambahan akan menjadi minimal.
supercat
Meskipun mungkin tidak bermanfaat untuk memiliki kelas dasar umum untuk semuanya, saya pikir ada beberapa kategori objek yang agak besar yang mana kelas dasar umum akan membantu. Sebagai contoh, banyak (pluralitas substansial jika bukan mayoritas) kelas di Jawa dapat digunakan dalam dua cara: sebagai pemegang bersama dari data yang dapat diubah, atau sebagai pemegang data yang dapat dibagi yang tidak boleh diubah oleh siapa pun. Dengan kedua pola penggunaan, pointer yang dikelola (referensi) digunakan sebagai proksi untuk data yang mendasarinya. Mampu memiliki tipe pointer dikelola yang umum untuk semua data tersebut sangat membantu.
supercat
16

Memiliki satu objek root membatasi apa yang dapat Anda lakukan dan apa yang dapat dilakukan oleh kompiler, tanpa banyak hasil.

Kelas root yang sama memungkinkan untuk membuat container-of-anything dan mengekstraksi apa yang mereka miliki dengan dynamic_cast, tetapi jika Anda membutuhkan container-of-apa pun maka sesuatu yang mirip boost::anydapat dilakukan tanpa kelas root yang sama. Dan boost::anyjuga mendukung primitif - bahkan dapat mendukung optimasi buffer kecil dan membuat mereka hampir "unboxed" dalam bahasa Java.

C ++ mendukung dan berkembang pada tipe nilai. Baik literal, dan programmer tipe nilai tertulis. Wadah C ++ secara efisien menyimpan, mengurutkan, hash, mengkonsumsi, dan menghasilkan jenis nilai.

Pewarisan, khususnya jenis warisan kelas dasar gaya Java yang menyiratkan monolitik, memerlukan jenis "penunjuk" atau "referensi" berbasis toko bebas. Pegangan / penunjuk / referensi Anda ke data memiliki penunjuk ke antarmuka kelas, dan secara polimorfis dapat mewakili hal lain.

Meskipun ini berguna dalam beberapa situasi, setelah Anda menikah dengan pola dengan "kelas dasar umum", Anda telah mengunci seluruh basis kode Anda ke dalam biaya dan bagasi dari pola ini, bahkan ketika itu tidak berguna.

Hampir selalu Anda tahu lebih banyak tentang jenis daripada "itu adalah objek" di salah satu situs panggilan, atau dalam kode yang menggunakannya.

Jika fungsinya sederhana, menulis fungsi sebagai templat memberi Anda polimorfisme berbasis waktu kompilasi tipe bebek di mana informasi di situs pemanggil tidak dibuang. Jika fungsi ini lebih kompleks, penghapusan tipe dapat dilakukan dimana operasi seragam pada jenis yang ingin Anda lakukan (katakanlah, serialisasi dan deserialisasi) dapat dibangun dan disimpan (pada waktu kompilasi) untuk dikonsumsi (pada saat run time) oleh kode dalam unit terjemahan yang berbeda.

Misalkan Anda memiliki beberapa perpustakaan di mana Anda ingin semuanya serializable. Salah satu pendekatan adalah memiliki kelas dasar:

struct serialization_friendly {
  virtual void write_to( my_buffer* ) const = 0;
  virtual void read_from( my_buffer const* ) = 0;
  virtual ~serialization_friendly() {}
};

Sekarang setiap bit kode yang Anda tulis bisa serialization_friendly.

void serialize( my_buffer* b, serialization_friendly const* x ) {
  if (x) x->write_to(b);
}

Kecuali bukan std::vector, jadi sekarang Anda perlu menulis setiap wadah. Dan bukan bilangan bulat yang Anda dapatkan dari perpustakaan bignum itu. Dan bukan tipe yang Anda tulis yang menurut Anda tidak perlu serialisasi. Dan bukan a tuple, atau a intatau a double, atau a std::ptrdiff_t.

Kami mengambil pendekatan lain:

void write_to( my_buffer* b, int x ) {
  b->write_integer(x);
}    
template<class T,
  class=std::enable_if_t< void_t<
    std::declval<T const*>()->write_to( std::declval<my_buffer*>()
  > >
>
void write_to( my_buffer* b, T const* x ) {
  if (x) x->write_to(b);
}
template<class T>
void serialize( my_buffer* b, T const& t ) {
  write_to( b, t );
}

yang terdiri dari, yah, tidak melakukan apa-apa, tampaknya. Kecuali sekarang kita dapat memperluas write_todengan mengesampingkan write_tosebagai fungsi bebas di namespace dari suatu tipe atau metode dalam tipe tersebut.

Kami bahkan dapat menulis sedikit kode tipe penghapusan:

namespace details {
  struct can_serialize_pimpl {
    virtual void write_to( my_buffer* ) const = 0;
    virtual void read_from( my_buffer const* ) = 0;
    virtual ~can_serialize_pimpl() {}
  };
}
struct can_serialize {
  void write_to( my_buffer* b ) const { pImpl->write_to(b); }
  void read_from( my_buffer const* b ) { pImpl->read_from(b); }
  std::unique_ptr<details::can_serialize_pimpl> pImpl;
  template<class T> can_serialize(T&&);
};
namespace details { 
  template<class T>
  struct can_serialize : can_serialize_pimpl {
    std::decay_t<T>* t;
    void write_to( my_buffer*b ) const final override {
      serialize( b, std::forward<T>(*t) );
    }
    void read_from( my_buffer const* ) final override {
      deserialize( b, std::forward<T>(*t) );
    }
    can_serialize(T&& in):t(&in) {}
  };
}
template<class T> can_serialize::can_serialize<T>(T&&t):pImpl(
  std::make_unique<details::can_serialize<T>>( std::forward<T>(t) );
) {}

dan sekarang kita dapat mengambil tipe yang sewenang-wenang dan memasukkannya secara otomatis ke sebuah can_serializeantarmuka yang memungkinkan Anda memohon serializepada titik selanjutnya melalui antarmuka virtual.

Begitu:

void writer_thingy( can_serialize s );

adalah fungsi yang mengambil apa pun yang bisa bersambung, bukan

void writer_thingy( serialization_friendly const* s );

dan yang pertama, tidak seperti kedua, dapat menangani int, std::vector<std::vector<Bob>>secara otomatis.

Tidak perlu banyak untuk menulisnya, terutama karena hal semacam ini adalah sesuatu yang jarang ingin Anda lakukan, tetapi kami memperoleh kemampuan untuk memperlakukan sesuatu sebagai serializable tanpa memerlukan jenis dasar.

Terlebih lagi, kita sekarang dapat menjadikan std::vector<T>serializable sebagai warga negara kelas satu hanya dengan mengesampingkan write_to( my_buffer*, std::vector<T> const& )- dengan kelebihan itu, itu dapat diteruskan ke a can_serializedan serializabilty dari std::vectordisimpan dalam tabel dan diakses oleh .write_to.

Singkatnya, C ++ cukup kuat sehingga Anda dapat menerapkan keuntungan dari satu kelas dasar on-the-fly saat diperlukan, tanpa harus membayar harga hierarki warisan paksa saat tidak diperlukan. Dan saat-saat ketika basis tunggal (palsu atau tidak) diperlukan cukup langka.

Ketika tipe sebenarnya identitas mereka, dan Anda tahu apa itu, peluang optimasi berlimpah. Data disimpan secara lokal dan berdekatan (yang sangat penting untuk keramahan cache pada prosesor modern), kompiler dapat dengan mudah memahami apa operasi yang diberikan (alih-alih memiliki penunjuk metode virtual buram yang harus dilompati, untuk mengarah ke kode yang tidak diketahui pada prosesor sisi lain) yang memungkinkan instruksi disusun kembali secara optimal, dan pasak bundar yang lebih sedikit dipalu menjadi lubang bundar.

Yakk
sumber
8

Ada banyak jawaban bagus di atas, dan fakta jelas bahwa apa pun yang akan Anda lakukan dengan kelas-dasar-semua-objek dapat dilakukan dengan lebih baik dengan cara lain seperti yang ditunjukkan oleh jawaban @ ratchetfreak dan komentar tentang itu sangat penting, tetapi ada alasan lain, yaitu untuk menghindari membuat berlian warisanketika pewarisan berganda digunakan. Jika Anda memiliki fungsionalitas dalam kelas dasar universal, segera setelah Anda mulai menggunakan beberapa pewarisan Anda harus mulai menentukan varian mana yang ingin Anda akses, karena itu bisa kelebihan beban berbeda di jalur berbeda dari rantai pewarisan. Dan basisnya tidak bisa virtual, karena ini akan sangat tidak efisien (membutuhkan semua objek untuk memiliki tabel virtual dengan potensi biaya yang sangat besar dalam penggunaan memori dan lokalitas). Ini akan menjadi mimpi buruk logistik dengan sangat cepat.

Jules
sumber
1
Salah satu solusi untuk masalah intan adalah memiliki semua jenis yang secara non-virtual menghasilkan tipe dasar melalui beberapa jalur yang menimpa semua anggota virtual dari tipe dasar tersebut; jika tipe dasar umum telah dibangun ke dalam bahasa sejak awal, kompiler dapat secara otomatis menghasilkan implementasi standar yang sah (meskipun tidak selalu mengesankan).
supercat
5

Bahkan Microsoft awal C ++ kompiler dan perpustakaan (saya tahu tentang Visual C ++, 16 bit) memiliki kelas seperti itu CObject.

Namun Anda harus tahu bahwa pada saat itu "templat" tidak didukung oleh kompiler C ++ sederhana ini sehingga kelas seperti std::vector<class T>tidak dimungkinkan. Sebaliknya implementasi "vektor" hanya bisa menangani satu jenis kelas sehingga ada kelas yang sebanding dengan std::vector<CObject>hari ini. Karena CObjectitu adalah kelas dasar dari hampir semua kelas (sayangnya tidak CString- setara stringdengan kompiler modern) Anda dapat menggunakan kelas ini untuk menyimpan hampir semua jenis objek.

Karena kompiler modern mendukung templat, kasus penggunaan "kelas dasar generik" ini tidak lagi diberikan.

Anda harus memikirkan fakta bahwa menggunakan kelas dasar generik seperti itu akan memakan biaya (sedikit) memori dan runtime - misalnya dalam panggilan ke konstruktor. Jadi ada kekurangan saat menggunakan kelas seperti itu tetapi setidaknya ketika menggunakan kompiler C ++ modern hampir tidak ada kasus penggunaan untuk kelas seperti itu.

Martin Rosenau
sumber
3
Apakah itu MFC? [comment padding]
user253751
3
Itu memang MFC. Suar yang bersinar dari desain OO yang menunjukkan kepada dunia bagaimana hal-hal harus dilakukan. Oh, tunggu ...
gbjbaanb
4
@gbjbaanb Turbo Pascal dan Turbo C ++ punya sendiri TObjectsebelum MFC ada. Jangan salahkan Microsoft untuk bagian desain itu, sepertinya ide bagus untuk hampir semua orang di masa itu.
hvd
Bahkan sebelum templat, mencoba menulis Smalltalk di C ++ menghasilkan hasil yang mengerikan.
JDługosz
@hvd Namun, MFC adalah contoh desain berorientasi objek yang jauh lebih buruk daripada apa pun yang dihasilkan Borland.
Jules
5

Saya akan menyarankan alasan lain yang berasal dari Jawa.

Karena Anda tidak dapat membuat kelas dasar untuk semuanya setidaknya tidak tanpa setumpuk boiler-plate.

Anda mungkin bisa lolos dengan itu untuk kelas Anda sendiri - tetapi Anda mungkin akan menemukan bahwa Anda akhirnya menduplikasi banyak kode. Misalnya "Saya tidak bisa menggunakan di std::vectorsini karena tidak menerapkan IObject- Saya lebih baik membuat turunan baru IVectorObjectyang melakukan hal yang benar ...".

Ini akan menjadi kasus setiap kali Anda berurusan dengan built-in atau kelas perpustakaan standar atau kelas dari perpustakaan lain.

Sekarang jika itu dibangun ke bahasa Anda akan berakhir dengan hal-hal seperti Integerdan intkebingungan yang ada di java, atau perubahan besar ke sintaks bahasa. (Ingat, saya pikir beberapa bahasa lain telah melakukan pekerjaan yang bagus dengan membangunnya ke dalam setiap jenis - ruby ​​sepertinya contoh yang lebih baik.)

Juga perhatikan bahwa jika kelas dasar Anda tidak run-time polymorphic (yaitu menggunakan fungsi virtual), Anda bisa mendapatkan manfaat yang sama dari menggunakan kerangka kerja seperti sifat.

mis. alih-alih .toString()Anda dapat memiliki yang berikut: (CATATAN: Saya tahu Anda bisa melakukan ini lebih rapi menggunakan perpustakaan yang ada dll, itu hanya contoh ilustrasi.)

template<typename T>
struct ToStringTrait;

template<typename T> 
std::string toString(const T & t) {
  return ToStringTrait<T>::toString(t);
}

template<>
struct ToStringTrait<int> {
  std::string toString(int v) {
    return itoa(v);
  }
}

template<typename T>
struct ToStringTrait<std::vector<T>> {
  std::string toString(const std::vector<T> &v) {
    std::stringstream ss;
    ss<<"{";
    for(int i=0; i<v.size(); ++i) {
      ss<<toString(v[i]);
    }
    ss<<"}";
    return ss.str();
  }
}
Michael Anderson
sumber
3

Boleh dibilang "batal" memenuhi banyak peran kelas dasar universal. Anda dapat menggunakan pointer apa saja ke a void*. Anda kemudian dapat membandingkan pointer tersebut. Anda dapat static_castkembali ke kelas asli.

Namun apa yang tidak dapat Anda lakukan dengan voidyang dapat Anda lakukan Objectadalah menggunakan RTTI untuk mencari tahu jenis objek yang Anda miliki. Ini pada akhirnya ke bagaimana tidak semua objek di C ++ memiliki RTTI, dan memang mungkin untuk memiliki objek dengan lebar nol.

pjc50
sumber
1
Hanya sub-objek baseclass lebar-nol, bukan sub-objek standar.
Deduplicator
@Dupuplikator Dengan cara memperbarui, C ++ 17 menambahkan [[no_unique_address]], yang dapat digunakan oleh kompiler untuk memberikan lebar nol kepada sub-objek anggota.
underscore_d
1
@underscore_d Maksud Anda berencana untuk C ++ 20, [[no_unique_address]]akan memungkinkan kompiler untuk variabel anggota EBO.
Deduplicator
@Dupuplikator Whoops, yup. Saya sudah mulai menggunakan C ++ 17, tapi saya rasa saya masih berpikir itu lebih canggih daripada yang sebenarnya!
underscore_d
2

Java mengambil filosofi desain bahwa Perilaku Tidak Terdefinisi seharusnya tidak ada . Kode seperti:

Cat felix = GetCat();
Woofer Rover = (Woofer)felix;
Rover.woof();

akan menguji apakah felixmemiliki subtipe Catantarmuka yang mengimplementasikan Woofer; jika ya, ia akan melakukan pemeran dan memanggil woof()dan jika tidak, itu akan melempar pengecualian. Perilaku kode sepenuhnya ditentukan apakah felixmenerapkan Wooferatau tidak .

C ++ mengambil filosofi bahwa jika suatu program tidak boleh mencoba beberapa operasi, tidak masalah apa pun kode yang dihasilkan akan dilakukan jika operasi itu dicoba, dan komputer tidak boleh membuang waktu mencoba membatasi perilaku dalam kasus yang "harus" tidak pernah muncul. Dalam C ++, menambahkan operator tipuan yang tepat sehingga untuk melemparkan *Catke *Woofer, kode akan menghasilkan perilaku yang ditentukan ketika para pemain sah, tetapi Perilaku tidak terdefinisi saat tidak .

Memiliki tipe pangkalan yang sama untuk beberapa hal memungkinkan untuk memvalidasi gips di antara turunan dari tipe dasar itu, dan juga untuk melakukan operasi uji coba, tetapi memvalidasi gips lebih mahal daripada sekadar mengasumsikan bahwa mereka sah dan berharap tidak ada hal buruk yang terjadi. Filosofi C ++ adalah bahwa validasi seperti itu membutuhkan "pembayaran untuk sesuatu yang Anda [biasanya] tidak perlu".

Masalah lain yang berhubungan dengan C ++, tetapi tidak akan menjadi masalah untuk bahasa baru, adalah bahwa jika beberapa programmer masing-masing membuat basis bersama, menurunkan kelas mereka sendiri dari itu, dan menulis kode untuk bekerja dengan hal-hal dari kelas dasar yang sama, kode seperti itu tidak akan dapat bekerja dengan objek yang dikembangkan oleh programmer yang menggunakan kelas dasar yang berbeda. Jika bahasa baru mengharuskan semua objek tumpukan memiliki format tajuk bersama, dan tidak pernah mengizinkan objek tumpukan yang tidak, maka metode yang memerlukan referensi ke objek tumpukan dengan header seperti itu akan menerima referensi ke objek tumpukan apa pun siapa pun pernah bisa membuat.

Secara pribadi, saya berpikir bahwa memiliki sarana umum untuk menanyakan objek "apakah Anda dapat dikonversi ke tipe X" adalah fitur yang sangat penting dalam bahasa / kerangka kerja, tetapi jika fitur tersebut tidak dibangun ke dalam bahasa sejak awal, sulit untuk tambahkan nanti. Secara pribadi, saya pikir kelas dasar seperti itu harus ditambahkan ke perpustakaan standar pada kesempatan pertama, dengan rekomendasi yang kuat bahwa semua objek yang akan digunakan secara polimorfik harus diwarisi dari basis itu. Memiliki pemrogram masing-masing mengimplementasikan "tipe dasar" mereka sendiri akan membuat objek yang lewat di antara kode orang yang berbeda lebih sulit, tetapi memiliki tipe dasar yang sama yang diwarisi oleh banyak programmer akan membuatnya lebih mudah.

TAMBAHAN

Dengan menggunakan templat, dimungkinkan untuk mendefinisikan "pemegang objek arbitrer" dan menanyakannya tentang jenis objek yang terkandung di dalamnya; paket Boost berisi hal yang disebut any. Jadi, meskipun C ++ tidak memiliki tipe standar "type-checkable reference to anything", dimungkinkan untuk membuatnya. Ini tidak memecahkan masalah yang disebutkan dengan tidak memiliki sesuatu dalam standar bahasa, yaitu ketidakcocokan antara implementasi programer yang berbeda, tetapi itu menjelaskan bagaimana C ++ bertahan tanpa memiliki tipe dasar dari mana semuanya diturunkan: dengan memungkinkan untuk membuat sesuatu yang bertindak seperti satu.

supercat
sumber
Para pemain gagal pada waktu kompilasi di C ++ , Java dan C # .
milleniumbug
1
@milleniumbug: Jika Woofermerupakan antarmuka dan Catdapat diwarisi, para pemain akan sah karena mungkin ada (jika tidak sekarang, mungkin di masa depan) WoofingCatyang mewarisi dari Catdan mengimplementasikan Woofer. Perhatikan bahwa di bawah model kompilasi / penautan Java, pembuatan a WoofingCattidak akan memerlukan akses ke kode sumber untuk Catatau Woofer.
supercat
3
C ++ memiliki dynamic_cast , yang menangani dengan benar mencoba melakukan cast dari a Catke a Wooferdan akan menjawab pertanyaan "apakah Anda convertible ke tipe X". C ++ akan memungkinkan Anda untuk memaksa para pemeran, karena hei, mungkin Anda benar-benar tahu apa yang Anda lakukan, tetapi itu juga akan membantu Anda jika bukan itu yang ingin Anda lakukan.
Rob K
2
@RobK: Anda benar tentang sintaks tentu saja; MEA Culpa. Saya telah membaca sedikit lebih banyak tentang dynamic_cast dan tampaknya dalam arti modern C ++ memiliki semua objek polimorfik yang berasal dari basis "objek polimorfik" kelas dasar dengan bidang apa pun yang diperlukan untuk mengidentifikasi jenis objek (biasanya tabel) pointer, meskipun itu detail implementasi). C ++ tidak menggambarkan kelas polimorfik seperti itu, tetapi meneruskan pointer ke dynamic_castperilaku yang telah ditentukan jika menunjuk ke objek polimorfik, dan Perilaku Tidak Terdefinisi jika tidak, jadi dari perspektif semantik ...
supercat
2
... semua objek polimorfik menyimpan beberapa informasi dengan tata letak yang sama, dan semua mendukung perilaku yang tidak didukung oleh objek non-polimorfik; menurut saya, itu berarti mereka berperilaku seolah-olah mereka berasal dari basis yang sama, apakah definisi bahasa menggunakan terminologi seperti itu atau tidak.
supercat
1

Symbian C ++ sebenarnya memiliki kelas dasar universal, CBase, untuk semua objek yang berperilaku dengan cara tertentu (terutama jika mereka mengalokasikan tumpukan). Ini memberikan destruktor virtual, memusatkan memori kelas pada konstruksi, dan menyembunyikan copy constructor.

Alasan di balik itu adalah bahasa untuk sistem embedded dan kompiler dan spesifikasi C ++ benar-benar sangat buruk 10 tahun yang lalu.

Tidak semua kelas diwarisi dari ini, hanya beberapa.

James
sumber