(Com o apagamento de tipo, quero dizer ocultar algumas ou todas as informações de tipo sobre uma classe, como Boost.Any .)
Quero conhecer as técnicas de apagamento de tipo, além de compartilhar as que eu conheço. Minha esperança é encontrar uma técnica maluca que alguém tenha pensado em sua hora mais sombria. :)
A primeira e mais óbvia, e comumente adotada abordagem, que eu sei, são funções virtuais. Apenas oculte a implementação de sua classe dentro de uma hierarquia de classes baseada em interface. Muitas bibliotecas Boost fazem isso, por exemplo, Boost.Any faz isso para ocultar seu tipo e Boost.Shared_ptr faz isso para ocultar a mecânica de (des) alocação.
Depois, há a opção com ponteiros de função para funções de modelo, enquanto mantém o objeto real em um void*
ponteiro, como o Boost.Function faz para ocultar o tipo real do functor. Implementações de exemplo podem ser encontradas no final da pergunta.
Então, para minha pergunta real:
Que outras técnicas de apagamento você conhece? Forneça, se possível, um código de exemplo, casos de uso, sua experiência com eles e talvez links para leitura adicional.
Editar
(Como eu não tinha certeza se gostaria de adicionar isso como resposta ou apenas editar a pergunta, farei a mais segura.)
Outra técnica interessante para ocultar o tipo real de algo sem funções virtuais ou void*
mexer, é o um GMan emprega aqui , com relevância para a minha pergunta sobre como exatamente isso funciona.
Código de exemplo:
#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
não reflete isso, sempre será o mesmo, shared_ptr<int>
por exemplo, ao contrário do contêiner padrão.
As
função não seria implementada dessa maneira. Como eu disse, de maneira alguma é seguro de usar! :)
function
, shared_ptr
, any
, Etc.? Todos eles empregam apagamento de tipo para conveniência do usuário.