类型擦除技术

| (使用类型擦除,我的意思是隐藏有关某个类的一些或所有类型信息,有点像Boost.Any。) 我想掌握类型擦除技术,同时也分享我所知道的那些技术。我的希望是找到一些在他/她最黑暗的时刻想到的疯狂技术。 :) 我知道,第一个也是最常见的方法是虚函数。只需在基于接口的类层次结构中隐藏类的实现即可。许多Boost库都执行此操作,例如Boost.Any执行此操作以隐藏您的类型,Boost.Shared_ptr执行此操作以隐藏(取消)分配机制。 然后是带有指向模板化函数的函数指针的选项,同时将实际对象保持在
void*
指针中,就像Boost.Function确实隐藏了函子的真实类型。示例实现可以在问题的末尾找到。 因此,对于我的实际问题: 您还知道其他哪些类型的擦除技术?请为他们提供示例代码,用例,您的使用经验以及可能的链接,以供进一步阅读。 编辑 (由于我不确定是否可以将此问题添加为答案,或者只是编辑问题,因此我会比较安全。) 隐藏人的实际类型的另一种不错的技术是不使用虚函数或“ 0”摆弄,这是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();
}
    
已邀请:
C ++中的所有类型擦除技术都是通过函数指针(用于行为)和
void*
(用于数据)来完成的。 “不同”方法的不同之处仅在于它们添加语义糖的方式不同。虚拟功能,例如,对于
struct Class {
    struct vtable {
        void (*dtor)(Class*);
        void (*func)(Class*,double);
    } * vtbl
};
iow:函数指针。 也就是说,我特别喜欢一种技术:5英镑,这仅仅是因为它使那些不知道可以执行此操作的人大吃一惊:您可以将数据存储在5英镑中,并且最后仍然调用了正确的析构函数,因为
shared_ptr
构造函数是一个函数模板,并且默认情况下将使用传递的实际对象的类型来创建删除器:
{
    const shared_ptr<void> sp( new A );
} // calls A::~A() here
当然,这只是通常的
void*
/功能指针类型的擦除,但打包起来非常方便。     
从根本上讲,这些是您的选择:虚拟函数或函数指针。 存储数据并将其与功能关联的方式可能会有所不同。例如,您可以存储指向基础的指针,并让派生类包含数据和虚函数实现,或者您可以将数据存储在其他地方(例如,在单独分配的缓冲区中),而只是让派生类提供虚函数实现,它取一个指向数据的“ 0”。如果将数据存储在单独的缓冲区中,则可以使用函数指针而不是虚拟函数。 在这种情况下,即使要单独存储数据,也要对类型擦除的数据进行多种操作,存储指向基础的指针也能很好地工作。否则,您将获得多个函数指针(每个类型擦除的函数一个),或者带有指定执行操作的参数的函数。     
我还将考虑(类似于
void*
)使用\“ raw storage \”:
char buffer[N]
。 在C ++ 0x中,您需要
std::aligned_storage<Size,Align>::type
。 您可以在其中存储任何想要的东西,只要它足够小并且可以正确处理对齐方式即可。     
Stroustrup在C ++编程语言(第4版)第25.3节中指出:   使用多种类型的值的单个欠时表示并依靠(静态)类型系统以确保仅根据声明的类型使用它们的技术的变型称为类型擦除。 特别是,如果我们使用模板,则不需要使用虚拟函数或函数指针来执行类型擦除。在其他答案中已经提到的根据ѭ14中存储的类型进行正确的析构函数调用的情况就是一个例子。 Stroustrup的书中提供的示例也很有趣。 考虑实现
template<class T> class Vector
,这是沿着
std::vector
的容器。当您经常在各种不同的指针类型上使用ѭ17时,编译器会为每种指针类型生成不同的代码。 通过为by0ѭ指针定义Vector的特殊化,然后将此特殊化用作所有其他类型ѭ20using的
Vector<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*
之后。     
请参阅以下系列文章,以获取(相当简短的)类型擦除技术列表以及有关权衡的讨论: 第一部分 第二部分 第三部分 第四部分 我还没有提到的是Adobe.Poly和Boost.Variant,它们在某种程度上可以被视为类型擦除。     
正如Marc所说,可以使用强制转换
std::shared_ptr<void>
。 例如,将类型存储在函数指针中,对其进行强制转换并将其存储在仅一种类型的函子中:
#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
    // !
}
    

要回复问题请先登录注册