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::any
dapat dilakukan tanpa kelas root yang sama. Dan boost::any
juga 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 int
atau 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_to
dengan mengesampingkan write_to
sebagai 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_serialize
antarmuka yang memungkinkan Anda memohon serialize
pada 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_serialize
dan serializabilty dari std::vector
disimpan 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.