지난 번에 함수 포인터로 함수명을 변경하지 못했다. 그 원인은 클래스 내부 함수의 주소는 클래스 주소 + 클래스 내부 함수 주소 로 되어있기 때문이다.
이후에 간간히 내 작업을 하면서 방법을 고민했다. 그러던중에 stl에서 지원하는 함수 포인터가 있다는 말을 들었다.
C++11에서부터 지원하는 이것은
의 단점을 보완하고
의 장점을 지닌다.
일련의 인자들로 함수에 바인딩시킨 함수 객체를 반환하는 함수 템플릿
std::placeHolder를 활용하여 추가 인수 bind bind의 인자 대신 std::placeHolders::_1, _2, _3... _N을 입력
클래스 멤버함수를 바인딩할 경우, 해당 객체를 받아야한다. 문제는 객체를 그대로 인자로 넘겨주면 복사가 발생하기에 std::ref 를 이용하여 참조로 넘겨야한다.
이것을 이용해서 이전에 내가 만들려던 상속받은 클래스의 함수 이름을 변경하는 기능을 만들 수 있었다.
template <typename T>
class Container {
public:
virtual T* Push() { //... }
protected: //....
};
class Object {
public: //...
};
class ObjectContainer
:public Container<Object> {
public:
typedef Object* (Container::*Push)();
Push PushObject = &Container::Push;
};
int main() {
ObjectContainer obj_container;
obj_container.PushOjbect();
}
이게 이전의 코드다. 이 코드의 문제는 컴파일러가 멤버함수를 호출하기위한 클래스의 주소를 모른다는 점이다. 이를 알려줄 수는 있지만,
(obj_container.*(obj_container->PushOjbect))();
로 명시해줘야기에 적어도 내가 보기엔 좋지못했다.
하지만 std::bind의 두번째인자에 객체를 알려주기에 이런 문제점을 해결할 수 있다.
template <typename T>
class Container
{
protected:
virtual void Push() { printf("Container::Push \n"); }
};
class Object
{
public:
//...
};
class ObjectContainer
:public Container<Object>
{
public:
ObjectContainer() {
f = std::bind(&ObjectContainer::Push, std::ref(*this));
f();
}
std::function<void()> f;
};
int main()
{
ObjectContainer obj_container;
return 0;
}
음...이걸 만들면서 대체품으로 생각했던게 상속받는 클래스안에 그냥 해당 함수를 호출하는 함수로 래핑시키는게 있었다.
나쁘지않았고, 오히려 그게 더 정답이었다고 생각했다. 그래도 일단 만들어보자했으니 만들었고, 이 두 개는 무슨 차이가 있을까싶어서 비교해보았다.
class ObjectContainer
:public Container<Object>
{
public:
ObjectContainer() {
f = std::bind(&ObjectContainer::Push, std::ref(*this));
cout << sizeof(f) << endl;
cout << sizeof(&ObjectContainer::Push) << endl;
cout << sizeof(&ObjectContainer::ObjectPush) << endl;
}
std::function<void()> f;
void ObjectPush() { Push(); }
};
int main()
{
ObjectContainer obj_container;
obj_container.f();
obj_container.ObjectPush();
getchar();
return 0;
}
나는 디스어셈블리를 볼 줄 모른다. 그냥 어셈블리어는 명령어가 단순하니 이런 뜻이겠거니~하고 유추할뿐.
아니라면 어쩔 수 없지ㅎㅎ 라는 가벼운 느낌으로 비교하는거니 넘겨도 된다.
애초에 이렇게 비교하는게 맞는지도 모르고..
void ObjectPush() { Push(); }
000000013FE92B20 mov qword ptr [rsp+8],rcx
000000013FE92B25 push rbp
000000013FE92B26 push rdi
000000013FE92B27 sub rsp,0E8h
000000013FE92B2E lea rbp,[rsp+20h]
000000013FE92B33 mov rdi,rsp
000000013FE92B36 mov ecx,3Ah
000000013FE92B3B mov eax,0CCCCCCCCh
000000013FE92B40 rep stos dword ptr [rdi]
000000013FE92B42 mov rcx,qword ptr [rsp+108h]
000000013FE92B4A mov rax,qword ptr [this]
000000013FE92B51 mov rax,qword ptr [rax]
000000013FE92B54 mov rcx,qword ptr [this]
000000013FE92B5B call qword ptr [rax]
000000013FE92B5D lea rsp,[rbp+0C8h]
000000013FE92B64 pop rdi
음 정확하게는 모르겠지만, ObjectPush의 메모리를 할당하는 것같다.
문제는 function이다. 어쨋든 function도 변수라서 큰 ...이걸 std::bind를 호출하는 순간이 문제다. stl인 function.h으로 들어가보면 알겠지만, 이거 완전 템플릿으로 떡칠해놨다.
큰 가락만 파악하고 포기했었는데, Binder라는 템플릿 클래스가 있다.
Binder는 당연히 함수의 주소와 객체(멤버함수라면), 함수의 인자를 저장한다.
_Compressed_pair에 객체 주소와 인자들이 저장된다. _Compressed_pair는
template <class First, class... _Types>
class Binder{
_Compressed_pair<decay_t<First>, tuple<decay_t<_Types>...>>
//... }
로 선언되어있는데, 이 decay_t는
using type = conditional_t<is_array_v<_Ty1>,
add_pointer_t<remove_extent_t<_Ty1>>,
conditional_t<is_function_v<_Ty1>,
add_pointer_t<_Ty1>,
remove_cv_t<_Ty1>>>;
으로 정의되어있다, 지금은 일단 패스하겠다. 이해도 못할 뿐더러 알아도 크게 상관이 없을 것같다.
여기서부터 도저히 모르겠다. 구조도 모르겠다. union을 저장공간으로 이용하는 곳부터 이해가 되질 않았기에 멈추었다.
말이 다른 곳으로 새어버렸는데, 말하고 싶은 것은 함수 하나를 저장하는 것이 자식 클래스에서 함수를 하나 생성하는 것보다 몇배나 많은 연산이 필요하다는 것이다.
obj_container.f();
000000013FA74C64 lea rcx,[rbp+20h]
000000013FA74C68 call std::_Func_class<void>::operator() (013FA716C7h)
obj_container.ObjectPush();
000000013FA74C6D lea rcx,[obj_container]
000000013FA74C71 call ObjectContainer::ObjectPush (013FA71244h)
호출은 차이가 없다. 동일.
다음으로 비교한 것은 용량이었다. 과연 우리의 std::function의 크기는 얼마나 될까? sizeof로 비교해본 결과...
....용량면에서 std::function은 멤버함수보다 무려 8배나 더 크다.
음...std::function이 함수포인터보다 좋지않다는 결론을 쓰기는 했지만, 그렇다고 std::function의 강점을 무시할 수는 없다. 개인적인 프로젝트를 만들면서 std::function을 썼는데, 확실히 함수 포인터보다는 쓰기도 쉽고 확장성이 더 좋다.
이 경우에는, 내가 용도에 맞지않게 썼다는 점이 맞을 것이다. 함수 포인터는 함수 포인터의 용도가, std::function에는 그에 맞는 용도가 있다.
std::tuple을 알아보자. (0) | 2018.04.26 |
---|---|
Lambda (람다) (0) | 2018.04.25 |
Epoll (0) | 2018.03.22 |
함수포인터로 함수명 변경하기 (0) | 2018.03.22 |
declspec에 대해 (0) | 2018.02.23 |
댓글,
Lowpoly
게임 서버 프로그래머 지망생