C 스타일의 vtable을 사용하여 할당없이 유형 삭제를 사용할 수 있습니다.
첫째, 개인 공간에서 VTABLE 세부 정보 :
namespace details {
template<class R, class...Args>
using call_view_sig = R(void const volatile*, Args&&...);
template<class R, class...Args>
struct call_view_vtable {
call_view_sig<R, Args...> const* invoke = 0;
};
template<class F, class R, class...Args>
call_view_sig<R, Args...>const* get_call_viewer() {
return [](void const volatile* pvoid, Args&&...args)->R{
F* pf = (F*)pvoid;
return (*pf)(std::forward<Args>(args)...);
};
}
template<class F, class R, class...Args>
call_view_vtable<R, Args...> make_call_view_vtable() {
return {get_call_viewer<F, R, Args...>()};
}
template<class F, class R, class...Args>
call_view_vtable<R, Args...>const* get_call_view_vtable() {
static const auto vtable = make_call_view_vtable<F, R, Args...>();
return &vtable;
}
}
템플릿 iteslf. std::function<Sig>
유사한 call_view<Sig>
을 호출됩니다
이 경우
template<class Sig>
struct call_view;
template<class R, class...Args>
struct call_view<R(Args...)> {
// check for "null":
explicit operator bool() const { return vtable && vtable->invoke; }
// invoke:
R operator()(Args...args) const {
return vtable->invoke(pvoid, std::forward<Args>(args)...);
}
// special member functions. No need for move, as state is pointers:
call_view(call_view const&)=default;
call_view& operator=(call_view const&)=default;
call_view()=default;
// construct from invokable object with compatible signature:
template<class F,
std::enable_if_t<!std::is_same<call_view, std::decay_t<F>>{}, int> =0
// todo: check compatibility of F
>
call_view(F&& f):
vtable(details::get_call_view_vtable< std::decay_t<F>, R, Args... >()),
pvoid(std::addressof(f))
{}
private:
// state is a vtable pointer and a pvoid:
details::call_view_vtable<R, Args...> const* vtable = 0;
void const volatile* pvoid = 0;
};
에서, vtable
조금 중복; 단일 함수에 대한 포인터 만 포함하는 구조. 우리가 지우는 하나 이상의 작업이있을 때 이것은 현명합니다. 이 경우에는 그렇지 않습니다.
vtable
을 그 한 가지 작업으로 대체 할 수 있습니다. 위의 vtable 작업 중 절반을 제거하면 구현이 더 간단 해집니다.
template<class Sig>
struct call_view;
template<class R, class...Args>
struct call_view<R(Args...)> {
explicit operator bool() const { return invoke; }
R operator()(Args...args) const {
return invoke(pvoid, std::forward<Args>(args)...);
}
call_view(call_view const&)=default;
call_view& operator=(call_view const&)=default;
call_view()=default;
template<class F,
std::enable_if_t<!std::is_same<call_view, std::decay_t<F>>{}, int> =0
>
call_view(F&& f):
invoke(details::get_call_viewer< std::decay_t<F>, R, Args... >()),
pvoid(std::addressof(f))
{}
private:
details::call_view_sig<R, Args...> const* invoke = 0;
void const volatile* pvoid = 0;
};
여전히 작동합니다.
약간의 리팩토링을 사용하여 삭제 유형의 값/참조 의미를 지워진 작업 유형에서 분할하기 위해 저장소 (또는 소유권)에서 디스패치 테이블을 분할 할 수 있습니다.
예를 들어, move-only 소유 호출 가능 코드는 위의 코드 대부분을 재사용해야합니다. 유형이 지워진 데이터가 스마트 포인터, void const volatile*
또는 std::aligned_storage
에 존재한다는 사실은 유형이 지워진 개체에서 수행 한 작업과 구분 될 수 있습니다. 다음과 같이
당신은 필요 값의 의미는, 당신은 유형의 삭제를 확장 할 경우 우리는 메모리의 경계 버퍼를 생성
namespace details {
using dtor_sig = void(void*);
using move_sig = void(void* dest, void*src);
using copy_sig = void(void* dest, void const*src);
struct dtor_vtable {
dtor_sig const* dtor = 0;
};
template<class T>
dtor_sig const* get_dtor() {
return [](void* x){
static_cast<T*>(x)->~T();
};
}
template<class T>
dtor_vtable make_dtor_vtable() {
return { get_dtor<T>() };
}
template<class T>
dtor_vtable const* get_dtor_vtable() {
static const auto vtable = make_dtor_vtable<T>();
return &vtable;
}
struct move_vtable:dtor_vtable {
move_sig const* move = 0;
move_sig const* move_assign = 0;
};
template<class T>
move_sig const* get_mover() {
return [](void* dest, void* src){
::new(dest) T(std::move(*static_cast<T*>(src)));
};
}
// not all moveable types can be move-assigned; for example, lambdas:
template<class T>
move_sig const* get_move_assigner() {
if constexpr(std::is_assignable<T,T>{})
return [](void* dest, void* src){
*static_cast<T*>(dest) = std::move(*static_cast<T*>(src));
};
else
return nullptr; // user of vtable has to handle this possibility
}
template<class T>
move_vtable make_move_vtable() {
return {{make_dtor_vtable<T>()}, get_mover<T>(), get_move_assigner<T>()};
}
template<class T>
move_vtable const* get_move_vtable() {
static const auto vtable = make_move_vtable<T>();
return &vtable;
}
template<class R, class...Args>
struct call_noalloc_vtable:
move_vtable,
call_view_vtable<R,Args...>
{};
template<class F, class R, class...Args>
call_noalloc_vtable<R,Args...> make_call_noalloc_vtable() {
return {{make_move_vtable<F>()}, {make_call_view_vtable<F, R, Args...>()}};
}
template<class F, class R, class...Args>
call_noalloc_vtable<R,Args...> const* get_call_noalloc_vtable() {
static const auto vtable = make_call_noalloc_vtable<F, R, Args...>();
return &vtable;
}
}
template<class Sig, std::size_t sz = sizeof(void*)*3, std::size_t algn=alignof(void*)>
struct call_noalloc;
template<class R, class...Args, std::size_t sz, std::size_t algn>
struct call_noalloc<R(Args...), sz, algn> {
explicit operator bool() const { return vtable; }
R operator()(Args...args) const {
return vtable->invoke(pvoid(), std::forward<Args>(args)...);
}
call_noalloc(call_noalloc&& o):call_noalloc()
{
*this = std::move(o);
}
call_noalloc& operator=(call_noalloc const& o) {
if (this == &o) return *this;
// moveing onto same type, assign:
if (o.vtable && vtable->move_assign && vtable == o.vtable)
{
vtable->move_assign(&data, &o.data);
return *this;
}
clear();
if (o.vtable) {
// moveing onto differnt type, construct:
o.vtable->move(&data, &o.data);
vtable = o.vtable;
}
return *this;
}
call_noalloc()=default;
template<class F,
std::enable_if_t<!std::is_same<call_noalloc, std::decay_t<F>>{}, int> =0
>
call_noalloc(F&& f)
{
static_assert(sizeof(std::decay_t<F>)<=sz && alignof(std::decay_t<F>)<=algn);
::new((void*)&data) std::decay_t<F>(std::forward<F>(f));
vtable = details::get_call_noalloc_vtable< std::decay_t<F>, R, Args... >();
}
void clear() {
if (!*this) return;
vtable->dtor(&data);
vtable = nullptr;
}
private:
void* pvoid() { return &data; }
void const* pvoid() const { return &data; }
details::call_noalloc_vtable<R, Args...> const* vtable = 0;
std::aligned_storage_t< sz, algn > data;
};
가에 객체를 저장하기 위해이 버전은 의미를 이동 지원합니다. 시맨틱을 복사하기위한 확장은 분명해야한다.
문제의 개체를 저장할 충분한 공간이 없으면 하드 컴파일러 오류가 발생한다는 점에서 std::function
보다 이점이 있습니다. 또한 비 할당 형으로서 할당 지연을 초래하지 않고 성능에 중요한 코드 내에서 사용할 수 있습니다.
테스트 코드 : 테스트 한 모든 3
void print_test(call_view< void(std::ostream& os) > printer) {
printer(std::cout);
}
int main() {
print_test([](auto&& os){ os << "hello world\n"; });
}
Live example.