(유형 삭제를 사용하면 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 : 함수 포인터.
그러나 내가 특히 좋아하는 기술이 하나 있습니다. 그것은 shared_ptr<void>
단순히 당신이 이것을 할 수 있다는 것을 모르는 사람들의 마음을 날려 버렸기 때문입니다 shared_ptr<void>
. shared_ptr
생성자는 함수 템플릿이고 기본적으로 삭제기를 생성하기 위해 전달 된 실제 객체의 유형을 사용 하기 때문에 end :
{
const shared_ptr<void> sp( new A );
} // calls A::~A() here
물론 이것은 일반적인 void*
/ function-pointer 유형 삭제이지만 매우 편리하게 패키지됩니다.
답변
기본적으로 가상 함수 또는 함수 포인터 옵션이 있습니다.
데이터를 저장하고 기능과 연결하는 방법은 다를 수 있습니다. 예를 들어, 포인터 대베이스를 저장하고 파생 클래스에 데이터 및 가상 함수 구현이 포함되도록 하거나 다른 위치에 데이터를 저장 (예 : 별도로 할당 된 버퍼에)하고 파생 클래스 만 제공 할 수 있습니다. void*
데이터를 가리키는 가상 함수 구현 . 데이터를 별도의 버퍼에 저장하면 가상 함수 대신 함수 포인터를 사용할 수 있습니다.
데이터가 별도로 저장되어 있어도 유형 소거 된 데이터에 적용하려는 여러 조작이있는 경우 포인터 대베이스 저장은이 컨텍스트에서 잘 작동합니다. 그렇지 않으면 여러 함수 포인터 (각 유형 소거 된 함수마다 하나씩) 또는 수행 할 작업을 지정하는 매개 변수가있는 함수로 끝납니다.
답변
또한 void*
“raw storage”의 사용을 고려할 것입니다 : char buffer[N]
.
C ++ 0x에서는이를 std::aligned_storage<Size,Align>::type
위한 것입니다.
충분히 작고 정렬을 올바르게 처리하는 한 원하는 것을 저장할 수 있습니다.
답변
Stroustrup 은 C ++ 프로그래밍 언어 (제 4 판) §25.3에 다음 과 같이 명시되어 있습니다.
여러 유형의 값에 단일 런트 타임 표현을 사용하고 선언 된 유형에 따라서 만 사용되도록하기 위해 (정적) 유형 시스템에 의존하는 기술의 변형을 유형 지우기 라고 합니다 .
특히 템플릿을 사용하는 경우 유형 삭제를 수행하기 위해 가상 함수 또는 함수 포인터를 사용할 필요가 없습니다. 다른 답변에서 이미 언급했듯이 a에 저장된 유형에 따른 올바른 소멸자 호출의 경우 std::shared_ptr<void>
가 그 예입니다.
Stroustrup의 책에 제공된 예는 마찬가지로 즐겁습니다.
template<class T> class Vector
라인을 따라 컨테이너를 구현 하는 것을 고려 하십시오 std::vector
. Vector
자주 발생하는 것처럼 포인터 유형을 많이 사용 하면 컴파일러는 모든 포인터 유형마다 다른 코드를 생성합니다.
이 코드 팽창 은 포인터 에 대한 Vector 의 전문화를 정의한 void*
다음이 전문화를 Vector<T*>
다른 모든 유형 의 공통 기본 구현으로 사용하여 방지 할 수 있습니다 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*
.
답변
유형 소거 기술의 (상당히 짧은) 목록과 트레이드 오프에 대한 토론은이 시리즈의 게시물을 참조하십시오 :
Part I ,
Part II ,
Part III ,
Part IV .
아직 언급하지 않은 것은 Adobe.Poly 및 Boost.Variant 이며 어느 정도 유형 삭제로 간주 될 수 있습니다.
답변
Marc가 말했듯이 cast 사용할 수 있습니다 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
// !
}
답변
