(Mit Typlöschung meine ich, einige oder alle Typinformationen zu einer Klasse zu verbergen , ähnlich wie Boost.Any .)
Ich möchte die Techniken zum Löschen von Typen kennenlernen und gleichzeitig die mir bekannten teilen. Meine Hoffnung ist es, eine verrückte Technik zu finden, an die jemand in seiner dunkelsten Stunde gedacht hat. :) :)
Der erste und offensichtlichste und am häufigsten verwendete Ansatz, den ich kenne, sind virtuelle Funktionen. Verstecken Sie einfach die Implementierung Ihrer Klasse in einer schnittstellenbasierten Klassenhierarchie. Viele Boost-Bibliotheken tun dies, zum Beispiel Boost.Any tut dies, um Ihren Typ auszublenden, und Boost.Shared_ptr tut dies, um die ( De- ) Zuordnungsmechanik auszublenden.
Dann gibt es die Option mit Funktionszeigern auf Vorlagenfunktionen, während das eigentliche Objekt in einem void*
Zeiger gehalten wird, wie dies Boost.Function tut, um den realen Typ des Funktors auszublenden. Beispielimplementierungen finden Sie am Ende der Frage.
Also, für meine eigentliche Frage:
Welche anderen Löschtechniken kennen Sie? Bitte geben Sie ihnen nach Möglichkeit einen Beispielcode, Anwendungsfälle, Ihre Erfahrungen mit ihnen und möglicherweise Links zur weiteren Lektüre.
Bearbeiten
(Da ich nicht sicher war, ob ich dies als Antwort hinzufügen oder nur die Frage bearbeiten sollte, mache ich einfach die sicherere.)
Eine andere nette Technik, um den tatsächlichen Typ von etwas ohne virtuelle Funktionen oder void*
Fummelei zu verbergen , ist das Ein GMan beschäftigt hier , was für meine Frage relevant ist, wie genau dies funktioniert.
Beispielcode:
#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
spiegelt dies nicht wider. Er ist shared_ptr<int>
beispielsweise im Gegensatz zum Standardcontainer immer derselbe .
As
Funktion nicht auf diese Weise implementiert. Wie gesagt, keineswegs sicher zu bedienen! :)
function
, shared_ptr
, any
Etc.? Sie alle verwenden Typlöschung für süße, süße Benutzerkomfort.