类型消除技术

(对于类型擦除,我的意思是隐藏关于类的部分或全部类型信息,有点像 提高,任何提高。)
我想掌握类型擦除技术,同时也分享这些,我知道。我希望能找到某人在最黑暗的时刻想到的疯狂技巧。:)

据我所知,第一个也是最明显、最常用的方法是虚函数。只需将类的实现隐藏在基于接口的类层次结构中即可。许多 Boost 库都这样做,例如 提高,任何提高这样做是为了隐藏类型,而 Boost. Shared _ ptr这样做是为了隐藏(反)分配机制。

还有一个选项是函数指针指向模板化函数,同时在 void*指针中保存实际对象,就像 推进,功能隐藏函数的实际类型一样。例子实现可以在问题的末尾找到。

所以,我真正的问题是:
你还知道其他类型的擦除技术吗?请提供他们,如果可能的话,一个示例代码,用例,你的经验与他们,也许进一步阅读的链接。

剪辑
(因为我不确定是否要添加这个作为答案,或者只是编辑这个问题,所以我还是选择更安全的答案。)
另一个隐藏实际类型的东西 没有虚拟函数或 void*提琴,是一个 GMan 使用的 给你,与 我的问题相关的确切工作原理。


示例代码:

#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();
}
34163 次浏览

我还会考虑(类似于 void*)使用“原始存储”: char buffer[N]

在 C + + 0x 中,这里有 std::aligned_storage<Size,Align>::type

你可以在那里存储任何你想要的东西,只要它足够小,并且你能正确处理对齐。

基本上,这些都是您的选项: 虚函数或函数指针。

存储数据并将其与函数关联的方式可能有所不同。例如,你可以存储一个指向基的指针,让派生类包含数据 还有的虚函数实现,或者你可以把数据存储在其他地方(例如,在一个单独分配的缓冲区) ,只要让派生类提供虚函数实现,它采用指向数据的 void*。如果将数据存储在单独的缓冲区中,则可以使用函数指针而不是虚函数。

在这种情况下,存储指针到基的操作效果很好,即使数据是单独存储的,如果您希望对类型擦除的数据应用多个操作的话。否则,您将得到多个函数指针(每个类型擦除的函数一个) ,或者带有指定要执行的操作的参数的函数。

C + + 中的所有类型擦除技术都是通过函数指针(用于行为)和 void*(用于数据)完成的。“不同”的方法只是在添加语义糖的方式上有所不同。例如,虚函数只是

struct Class {
struct vtable {
void (*dtor)(Class*);
void (*func)(Class*,double);
} * vtbl
};

函数指针。

尽管如此,有一种技术我特别喜欢: 它是 shared_ptr<void>,仅仅是因为它让那些不知道你可以这样做的人大吃一惊: 你可以在 shared_ptr<void>中存储任何数据,并且仍然在最后调用正确的析构函数,因为 shared_ptr构造函数是一个函数模板,并且将使用默认情况下创建删除器所传递的实际对象的类型:

{
const shared_ptr<void> sp( new A );
} // calls A::~A() here

当然,这只是通常的 void*/函数指针类型的擦除,但是封装非常方便。

请参阅这一系列的文章,了解类型擦除技术(相当简短)以及关于权衡的讨论: 第 I 部分, 第 II 部, 第 III 部, 第 IV 部分。

我还没有看到提到的是 土坯,保利提升,变异,它们在某种程度上可以被认为是类型擦除。

正如马克所说,一个人可以使用铸造 std::shared_ptr<void>。 例如,将 abc 0存储在一个函数指针中,对其进行强制转换,然后将其存储在只有一种类型的函数中:

#include <iostream>
#include <memory>
#include <functional>


using voidFun = void(*)(std::shared_ptr<void>);


template<typename T>
void fun(std::shared_ptr<T> t)
{
std::cout << *t << std::endl;
}


int main()
{
std::function<void(std::shared_ptr<void>)> call;


call = reinterpret_cast<voidFun>(fun<std::string>);
call(std::make_shared<std::string>("Hi there!"));


call = reinterpret_cast<voidFun>(fun<int>);
call(std::make_shared<int>(33));


call = reinterpret_cast<voidFun>(fun<char>);
call(std::make_shared<int>(33));




// Output:,
// Hi there!
// 33
// !
}

Stroustrup 在 C++程式语言(第四版)25.3中写道:

对许多类型的值使用单个运行时表示并依赖(静态)类型系统来确保只根据其声明的类型使用它们的技术的变体被称为 类型删除

特别是,如果我们使用模板,就需要 不使用虚函数或函数指针来执行类型擦除。其他答案中已经提到,根据存储在 std::shared_ptr<void>中的类型调用正确的析构函数就是一个例子。

Stroustrup 书中提供的例子同样令人愉快。

考虑实现 template<class T> class Vector,一个沿着 std::vector线路的容器。当您将 Vector与许多不同的指针类型一起使用时(这种情况经常发生) ,编译器应该会为每个指针类型生成不同的代码。

通过为 void*指针定义 矢量的专门化,然后将这种专门化用作所有其他类型 TVector<T*>的公共基本实现,可以防止出现这种 代码膨胀:

template<typename T>
class Vector<T*> : private Vector<void*>{
// all the dirty work is done once in the base class only
public:
// ...
// static type system ensures that a reference of right type is returned
T*& operator[](size_t i) { return reinterpret_cast<T*&>(Vector<void*>::operator[](i)); }
};

正如您所看到的,我们有一个强类型容器,但是 Vector<Animal*>Vector<Dog*>Vector<Cat*>、 ... 将共享相同的(C + + 还有二进制)实现代码,其指针类型 被抹去了位于 void*之后。