(Con el borrado de tipo, me refiero a ocultar parte o la totalidad de la información de tipo con respecto a una clase, algo así como Boost.Any .)
Quiero obtener una serie de técnicas de borrado de tipo, al mismo tiempo que comparto las que conozco. Espero encontrar alguna técnica loca que alguien haya pensado en su hora más oscura. :)
El primer y más obvio, y enfoque comúnmente adoptado, que sé, son las funciones virtuales. Simplemente oculte la implementación de su clase dentro de una jerarquía de clases basada en la interfaz. Muchas bibliotecas de Boost hacen esto, por ejemplo Boost.Any hace esto para ocultar su tipo y Boost.Shared_ptr hace esto para ocultar la (des) asignación mecánica.
Luego está la opción con punteros de función para funciones con plantilla, mientras se mantiene el objeto real en un void*
puntero, como Boost.Function lo hace para ocultar el tipo real del functor. Se pueden encontrar implementaciones de ejemplo al final de la pregunta.
Entonces, para mi pregunta real:
¿Qué otro tipo de técnicas de borrado conoce? Proporcione, si es posible, un código de ejemplo, casos de uso, su experiencia con ellos y quizás enlaces para lecturas adicionales.
Editar
(Dado que no estaba seguro de si agregar esto como respuesta, o simplemente editar la pregunta, haré la más segura).
Otra buena técnica para ocultar el tipo real de algo sin funciones virtuales o void*
tocar el violín es un GMan emplea aquí , con relación a mi pregunta sobre cómo funciona exactamente esto.
Código de ejemplo:
#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
no refleja esto, siempre será el mismo, shared_ptr<int>
por ejemplo, a diferencia del contenedor estándar.
As
(s) función (s) no se implementaría de esa manera. Como dije, ¡de ninguna manera es seguro de usar! :)
function
, shared_ptr
, any
, Etc.? Todos emplean borrado tipo para la comodidad del usuario dulce dulce.