(W przypadku wymazywania typu mam na myśli ukrycie niektórych lub wszystkich informacji o typie dotyczących klasy, trochę jak Boost.Any ).
Chcę poznać techniki wymazywania typu, jednocześnie udostępniając te, które znam. Mam nadzieję, że znajdę jakąś szaloną technikę, o której ktoś pomyślał w swojej najciemniejszej godzinie. :)
Pierwszym i najbardziej oczywistym i powszechnie stosowanym podejściem, jakie znam, są funkcje wirtualne. Po prostu ukryj implementację swojej klasy w hierarchii klas opartej na interfejsie. Wiele bibliotek Boost robi to, na przykład Boost, robi to, aby ukryć twój typ, a Boost.Shared_ptr robi to, aby ukryć mechanikę (de) alokacji.
Następnie jest opcja ze wskaźnikami funkcji do funkcji szablonowych, podczas gdy rzeczywisty obiekt znajduje się we void*
wskaźniku, jak Boost. Funkcja robi to, aby ukryć rzeczywisty typ funktora. Przykładowe implementacje można znaleźć na końcu pytania.
Tak więc, odpowiadając na moje aktualne pytanie:
jakie inne techniki wymazywania typu znasz? Jeśli to możliwe, podaj im przykładowy kod, przypadki użycia, swoje doświadczenia z nimi i być może linki do dalszego czytania.
Edytuj
(Ponieważ nie byłem pewien, czy dodać to jako odpowiedź, czy po prostu edytować pytanie, zrobię po prostu bezpieczniejsze).
Inną fajną techniką ukrywania rzeczywistego typu czegoś bez funkcji wirtualnych lub void*
majstrowania jest jeden GMan, którego używa tutaj , z odniesieniem do mojego pytania, jak dokładnie to działa.
Przykładowy kod:
#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();
}
shared_ptr
nie odzwierciedla, zawsze będzie taki sam, shared_ptr<int>
na przykład w przeciwieństwie do standardowego kontenera.
As
(e) funkcja nie zostałaby zaimplementowana w ten sposób. Jak powiedziałem, w żadnym wypadku nie jest bezpieczny w użyciu! :)
function
, shared_ptr
, any
, Itd.? Wszystkie używają wymazywania typu dla wygody użytkownika słodkich słodyczy.