(Dengan tipe erasure, maksud saya menyembunyikan beberapa atau semua tipe informasi mengenai kelas, agak seperti Boost . Setiap .)
Saya ingin mengetahui teknik-teknik penghapusan tipe, sambil juga membagikan yang, yang saya tahu. Harapan saya agak untuk menemukan beberapa teknik gila yang dipikirkan seseorang di jam tergelapnya. :)
Yang pertama dan paling jelas, dan pendekatan yang biasa diambil, yang saya tahu, adalah fungsi virtual. Sembunyikan saja implementasi kelas Anda di dalam hierarki kelas berbasis antarmuka. Banyak pustaka Boost melakukan ini, misalnya Boost.Any melakukan ini untuk menyembunyikan tipe Anda dan Boost.Shared_ptr melakukan ini untuk menyembunyikan mekanik alokasi (de).
Lalu ada opsi dengan fungsi pointer ke fungsi templated, sambil memegang objek yang sebenarnya dalam void*
pointer, seperti Boost. Fungsi yang dilakukannya untuk menyembunyikan tipe nyata dari functor. Contoh implementasi dapat ditemukan di akhir pertanyaan.
Jadi, untuk pertanyaan saya yang sebenarnya:
Apa teknik penghapusan tipe lain yang Anda ketahui? Harap berikan kepada mereka, jika mungkin, dengan kode contoh, gunakan case, pengalaman Anda dengan mereka dan mungkin tautan untuk membaca lebih lanjut.
Sunting
(Karena saya tidak yakin apakah akan menambahkan ini sebagai jawaban, atau hanya mengedit pertanyaan, saya hanya akan melakukan yang lebih aman.)
Teknik lain yang bagus untuk menyembunyikan jenis sesuatu yang sebenarnya tanpa fungsi virtual atau void*
mengutak - atik, adalah satu GMan mempekerjakan di sini , dengan relevansi dengan pertanyaan saya tentang bagaimana tepatnya ini bekerja.
Kode contoh:
#include <iostream>
#include <string>
// NOTE: The class name indicates the underlying type erasure technique
// this behaves like the Boost.Any type w.r.t. implementation details
class Any_Virtual{
struct holder_base{
virtual ~holder_base(){}
virtual holder_base* clone() const = 0;
};
template<class T>
struct holder : holder_base{
holder()
: held_()
{}
holder(T const& t)
: held_(t)
{}
virtual ~holder(){
}
virtual holder_base* clone() const {
return new holder<T>(*this);
}
T held_;
};
public:
Any_Virtual()
: storage_(0)
{}
Any_Virtual(Any_Virtual const& other)
: storage_(other.storage_->clone())
{}
template<class T>
Any_Virtual(T const& t)
: storage_(new holder<T>(t))
{}
~Any_Virtual(){
Clear();
}
Any_Virtual& operator=(Any_Virtual const& other){
Clear();
storage_ = other.storage_->clone();
return *this;
}
template<class T>
Any_Virtual& operator=(T const& t){
Clear();
storage_ = new holder<T>(t);
return *this;
}
void Clear(){
if(storage_)
delete storage_;
}
template<class T>
T& As(){
return static_cast<holder<T>*>(storage_)->held_;
}
private:
holder_base* storage_;
};
// the following demonstrates the use of void pointers
// and function pointers to templated operate functions
// to safely hide the type
enum Operation{
CopyTag,
DeleteTag
};
template<class T>
void Operate(void*const& in, void*& out, Operation op){
switch(op){
case CopyTag:
out = new T(*static_cast<T*>(in));
return;
case DeleteTag:
delete static_cast<T*>(out);
}
}
class Any_VoidPtr{
public:
Any_VoidPtr()
: object_(0)
, operate_(0)
{}
Any_VoidPtr(Any_VoidPtr const& other)
: object_(0)
, operate_(other.operate_)
{
if(other.object_)
operate_(other.object_, object_, CopyTag);
}
template<class T>
Any_VoidPtr(T const& t)
: object_(new T(t))
, operate_(&Operate<T>)
{}
~Any_VoidPtr(){
Clear();
}
Any_VoidPtr& operator=(Any_VoidPtr const& other){
Clear();
operate_ = other.operate_;
operate_(other.object_, object_, CopyTag);
return *this;
}
template<class T>
Any_VoidPtr& operator=(T const& t){
Clear();
object_ = new T(t);
operate_ = &Operate<T>;
return *this;
}
void Clear(){
if(object_)
operate_(0,object_,DeleteTag);
object_ = 0;
}
template<class T>
T& As(){
return *static_cast<T*>(object_);
}
private:
typedef void (*OperateFunc)(void*const&,void*&,Operation);
void* object_;
OperateFunc operate_;
};
int main(){
Any_Virtual a = 6;
std::cout << a.As<int>() << std::endl;
a = std::string("oh hi!");
std::cout << a.As<std::string>() << std::endl;
Any_Virtual av2 = a;
Any_VoidPtr a2 = 42;
std::cout << a2.As<int>() << std::endl;
Any_VoidPtr a3 = a.As<std::string>();
a2 = a3;
a2.As<std::string>() += " - again!";
std::cout << "a2: " << a2.As<std::string>() << std::endl;
std::cout << "a3: " << a3.As<std::string>() << std::endl;
a3 = a;
a3.As<Any_Virtual>().As<std::string>() += " - and yet again!!";
std::cout << "a: " << a.As<std::string>() << std::endl;
std::cout << "a3->a: " << a3.As<Any_Virtual>().As<std::string>() << std::endl;
std::cin.get();
}
sumber
shared_ptr
tidak mencerminkan ini, itu akan selalu sama,shared_ptr<int>
misalnya, tidak seperti wadah standar.As
fungsi seperti itu tidak akan diimplementasikan seperti itu. Seperti saya katakan, tidak berarti aman digunakan! :)function
,shared_ptr
,any
, Dll? Mereka semua menggunakan tipe erasure untuk kenyamanan pengguna yang manis.Jawaban:
Semua jenis teknik penghapusan di C ++ dilakukan dengan pointer fungsi (untuk perilaku) dan
void*
(untuk data). Metode "berbeda" hanya berbeda dalam cara mereka menambahkan gula semantik. Fungsi virtual, misalnya, hanyalah gula semantikiow: pointer fungsi.
Yang mengatakan, ada satu teknik yang saya terutama suka: Ini
shared_ptr<void>
, hanya karena itu menghancurkan pikiran orang-orang yang tidak tahu Anda bisa melakukan ini: Anda dapat menyimpan data apa pun dalamshared_ptr<void>
, dan masih memiliki destructor yang benar dipanggil di akhir, karenashared_ptr
konstruktor adalah templat fungsi, dan akan menggunakan jenis objek aktual yang dilewati untuk membuat deleter secara default:Tentu saja, ini hanya
void*
penghapusan tipe biasa / fungsi-pointer, tetapi sangat mudah dikemas.sumber
shared_ptr<void>
teman saya dengan contoh implementasi beberapa hari yang lalu. :) Ini benar-benar keren.unique_ptr
tidak mengetik-menghapus deleter, jadi jika Anda ingin menetapkanunique_ptr<T>
aunique_ptr<void>
, Anda perlu memberikan argumen deleter, secara eksplisit, yang tahu cara menghapusT
avoid*
. Jika sekarang Anda ingin menetapkanS
, juga, maka Anda memerlukan deleter, secara eksplisit, yang tahu cara menghapus aT
melaluivoid*
dan jugaS
melaluivoid*
, dan , mengingatvoid*
, tahu apakah itu aT
atau aS
. Pada titik itu, Anda telah menulis deleter yang dihapus dengan tipeunique_ptr
, dan kemudian berhasil jugaunique_ptr
. Tidak keluar dari kotak.unique_ptr
?" Berguna untuk beberapa orang, tetapi tidak menjawab pertanyaan saya. Saya kira jawabannya adalah, karena pointer bersama mendapat lebih banyak perhatian dalam pengembangan perpustakaan standar. Yang saya pikir agak sedih karena pointer unik lebih sederhana, jadi seharusnya lebih mudah untuk menerapkan fungsi dasar, dan mereka lebih efisien sehingga orang harus menggunakannya lebih banyak. Sebaliknya, kita memiliki kebalikannya.Pada dasarnya, itu adalah opsi Anda: fungsi virtual atau pointer fungsi.
Bagaimana Anda menyimpan data dan mengaitkannya dengan fungsi dapat bervariasi. Sebagai contoh, Anda bisa menyimpan pointer-to-base, dan meminta kelas turunannya berisi data dan implementasi fungsi virtual, atau Anda bisa menyimpan data di tempat lain (misalnya dalam buffer yang dialokasikan secara terpisah), dan kelas turunannya menyediakan implementasi fungsi virtual, yang mengambil
void*
poin ke data. Jika Anda menyimpan data dalam buffer terpisah, maka Anda bisa menggunakan pointer fungsi daripada fungsi virtual.Menyimpan pointer-ke-pangkalan berfungsi dengan baik dalam konteks ini, bahkan jika data disimpan secara terpisah, jika ada beberapa operasi yang ingin Anda terapkan pada data yang dihapus tipe Anda. Kalau tidak, Anda berakhir dengan beberapa pointer fungsi (satu untuk masing-masing fungsi yang terhapus jenis), atau berfungsi dengan parameter yang menentukan operasi yang akan dilakukan.
sumber
Saya juga akan mempertimbangkan (mirip dengan
void*
) penggunaan "raw storage":char buffer[N]
.Dalam C ++ 0x yang Anda miliki
std::aligned_storage<Size,Align>::type
untuk ini.Anda dapat menyimpan apa pun yang Anda inginkan di sana, asalkan itu cukup kecil dan Anda menangani perataan dengan benar.
sumber
std::aligned_storage
, terima kasih! :)std::aligned_storage<...>::type
hanyalah penyangga mentah yang, tidak sepertichar [sizeof(T)]
, selaras sesuai. Dengan sendirinya, itu lembam: tidak menginisialisasi ingatannya, tidak membangun objek, tidak ada. Oleh karena itu, setelah Anda memiliki buffer jenis ini, Anda harus membuat objek secara manual di dalamnya (dengan metode penempatannew
atau pengalokasiconstruct
) dan Anda juga harus merusak objek di dalamnya secara manual (baik secara manual menggunakan destruktor mereka atau menggunakandestroy
metode alokasi . ).Stroustrup, dalam bahasa pemrograman C ++ (edisi ke-4) §25.3 , menyatakan:
Secara khusus, tidak diperlukan penggunaan fungsi virtual atau pointer fungsi untuk melakukan penghapusan tipe jika kita menggunakan templat. Kasing, sudah disebutkan dalam jawaban lain, panggilan destruktor yang benar sesuai dengan jenis yang disimpan dalam a
std::shared_ptr<void>
adalah contoh dari itu.Contoh yang diberikan dalam buku Stroustrup sama menyenangkannya.
Pikirkan tentang implementasi
template<class T> class Vector
, sebuah wadah di sepanjang garisstd::vector
. Ketika Anda akan menggunakan AndaVector
dengan banyak jenis pointer yang berbeda, seperti yang sering terjadi, kompiler seharusnya akan menghasilkan kode yang berbeda untuk setiap jenis pointer.Kode ini mengasapi dapat dicegah dengan mendefinisikan spesialisasi Vector untuk
void*
pointer dan kemudian menggunakan spesialisasi ini sebagai implementasi dasar umumVector<T*>
untuk semua jenis lainnyaT
:Seperti yang Anda lihat, kita memiliki wadah sangat diketik tapi
Vector<Animal*>
,Vector<Dog*>
,Vector<Cat*>
, ..., akan berbagi sama (C ++ dan biner) kode untuk implementasi, memiliki tipe pointer mereka terhapus belakangvoid*
.sumber
template<typename Derived> VectorBase<Derived>
yang kemudian dikhususkan sebagaitemplate<typename T> VectorBase<Vector<T*> >
. Selain itu, pendekatan ini tidak hanya berfungsi untuk pointer, tetapi untuk jenis apa pun.Lihat seri posting ini untuk daftar (cukup singkat) teknik penghapusan tipe dan diskusi tentang timbal balik: Bagian I , Bagian II , Bagian III , Bagian IV .
Yang belum saya lihat disebutkan adalah Adobe.Poly , dan Boost.Variant , yang dapat dianggap sebagai jenis penghapusan sampai batas tertentu.
sumber
Seperti yang dinyatakan oleh Marc, seseorang dapat menggunakan pemain
std::shared_ptr<void>
. Misalnya menyimpan jenis dalam pointer fungsi, melemparkannya dan menyimpannya di functor hanya satu jenis:sumber