Programing

유형 삭제 기술

lottogame 2020. 7. 1. 07:59
반응형

유형 삭제 기술


(유형 삭제를 사용하면 Boost.Any 와 같은 클래스에 대한 유형 정보의 일부 또는 전부를 숨기는 것을 의미합니다 .)
유형 삭제 기술을 유지하면서 알고있는 기술을 공유하고 싶습니다. 누군가가 자신의 가장 어두운 시간에 생각했던 미친 기술을 찾기를 바랍니다. :)

내가 아는 첫 번째이자 가장 명백하고 일반적으로 사용되는 접근 방식은 가상 기능입니다. 인터페이스 기반 클래스 계층 구조에서 클래스 구현을 숨기십시오. 많은 Boost 라이브러리가 이것을 수행합니다 (예 : Boost.Any 는 유형을 숨기고 이를 수행 하며 Boost.Shared_ptr 은 (de) 할당 메커니즘을 숨기려고합니다).

그런 다음 템플릿과 같은 함수 포인터가있는 옵션이 있으며 Boostvoid* 와 같은 포인터에 실제 객체를 보유하고 함수 는 실제 유형의 functor를 숨 깁니다. 질문의 끝에 구현 예를 찾을 수 있습니다.

내 실제 질문 :
당신은 다른 어떤 유형의 삭제 기술을 알고 있습니까? 가능한 경우 예제 코드, 사용 사례, 경험 및 추가 정보를 제공하는 링크를 제공하십시오.

편집
(이 답변을 추가하거나 질문을 편집하는 것이 확실하지 않기 때문에 더 안전한
방법을 사용 하겠습니다.) 가상 기능이나 방해 없이 실제 유형의 것을 숨기는 또 다른 멋진 기술 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();
}

C ++의 모든 유형 삭제 기술은 함수 포인터 (행동) 및 void*(데이터)로 수행됩니다. "서로 다른"방법은 시맨틱 설탕을 첨가하는 방식이 단순히 다릅니다. 가상 함수, 예를 들어 의미 적 설탕

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

iow : 함수 포인터.

That said, there's one technique I particularly like, though: It's shared_ptr<void>, simply because it blows the minds off of people who don't know you can do this: You can store any data in a shared_ptr<void>, and still have the correct destructor called at the end, because the shared_ptr constructor is a function template, and will use the type of the actual object passed for creating the deleter by default:

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

Of course, this is just the usual void*/function-pointer type erasure, but very conveniently packaged.


Fundamentally, those are your options: virtual functions or function pointers.

How you store the data and associate it with the functions can vary. For example, you could store a pointer-to-base, and have the derived class contain the data and the virtual function implementations, or you could store the data elsewhere (e.g. in a separately allocated buffer), and just have the derived class provide the virtual function implementations, which take a void* that points to the data. If you store the data in a separate buffer, then you could use function pointers rather than virtual functions.

Storing a pointer-to-base works well in this context, even if the data is stored separately, if there are multiple operations that you wish to apply to your type-erased data. Otherwise you end up with multiple function pointers (one for each of the type-erased functions), or functions with a parameter that specifies the operation to perform.


I would also consider (similar to void*) the use of "raw storage": char buffer[N].

In C++0x you have std::aligned_storage<Size,Align>::type for this.

You can store anything you want in there, as long as it's small enough and you deal with the alignment properly.


Stroustrup, in The C++ programming language (4th edition) §25.3, states:

Variants of the technique of using a single runt-time representation for values of a number of types and relying on the (static) type system to ensure that they are used only according to their declared type has been called type erasure.

In particular, no use of virtual functions or function pointers is needed to perform type erasure if we use templates. The case, already mentioned in other answers, of the correct destructor call according to the type stored in a std::shared_ptr<void> is an example of that.

The example provided in Stroustrup's book is just as enjoyable.

Think about implementing template<class T> class Vector, a container along the lines of std::vector. When you will use your Vector with a lot of different pointers types, as it often happens, the compiler will supposedly generate different code for every pointer type.

This code bloat can be prevented by defining a specialization of Vector for void* pointers and then using this specialization as a common base implementation of Vector<T*> for all others types 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)); }
};

As you can see, we have a strongly typed container but Vector<Animal*>, Vector<Dog*>, Vector<Cat*>, ..., will share the same (C++ and binary) code for the implementation, having their pointer type erased behind void*.


See this series of posts for a (fairly short) list of type erasure techniques and the discussion about the trade-offs: Part I, Part II, Part III, Part IV.

The one I haven't seen mentioned yet is Adobe.Poly, and Boost.Variant, which can be considered a type erasure to some extent.


As stated by Marc, one can use cast std::shared_ptr<void>. For example store the type in a function pointer, cast it and store in a functor of only one type:

#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
    // !
}

참고URL : https://stackoverflow.com/questions/5450159/type-erasure-techniques

반응형