(Avec l'effacement de type, je veux dire cacher tout ou partie des informations de type concernant une classe, un peu comme Boost.Any .)
Je veux mettre la main sur les techniques d'effacement de type, tout en partageant celles que je connais. Mon espoir est un peu de trouver une technique folle à laquelle quelqu'un a pensé à son heure la plus sombre. :)
La première approche, la plus évidente et la plus courante, que je connaisse, ce sont les fonctions virtuelles. Cachez simplement l'implémentation de votre classe dans une hiérarchie de classes basée sur une interface. De nombreuses bibliothèques Boost le font, par exemple Boost.Any le fait pour masquer votre type et Boost.Shared_ptr le fait pour masquer le mécanisme de (dé) allocation.
Ensuite, il y a l'option avec des pointeurs de fonction vers des fonctions modèles, tout en maintenant l'objet réel dans un void*
pointeur, comme Boost.Function le fait pour masquer le type réel du foncteur. Des exemples d'implémentations peuvent être trouvés à la fin de la question.
Donc, pour ma vraie question:
quelles autres techniques d'effacement de type connaissez-vous? Veuillez leur fournir, si possible, un exemple de code, des cas d'utilisation, votre expérience avec eux et peut-être des liens pour en savoir plus.
Modifier
(Étant donné que je n'étais pas sûr d'ajouter ceci comme réponse, ou simplement de modifier la question, je vais simplement faire la plus sûre.)
Une autre technique intéressante pour masquer le type réel de quelque chose sans fonctions virtuelles ni void*
violon est le un GMan emploie ici , en rapport avec ma question sur la façon dont cela fonctionne exactement.
Exemple de code:
#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
ne reflète pas cela, il sera toujours le même, shared_ptr<int>
par exemple, contrairement au conteneur standard.
As
fonction (s) ne serait pas implémentée de cette façon. Comme je l'ai dit, pas sûr à utiliser! :)
function
, shared_ptr
, any
, Etc.? Ils utilisent tous l'effacement de type pour un confort d'utilisation doux.