Post

왜 MemberFunction Pointer 를 cout 으로 출력하면 1이 나올까

주의 사항

이 글은 예전 블로그에서 옮겨온 오래 된 글입니다. 현재 상황과는 다를 수 있으며, 잘못 된 정보가 있을 수 있습니다.


C++ Korea 에서 질문을 하나 받았습니다.

왜 아래 코드를 출력하면 결과가 1인가요? 라는 질문이었지요

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Some
{
public:
    void func()
    {
        int i;
        i = 100;
        cout << i << endl;
    }
};
 
int main()
{
    cout << "Value : " << &Some::func << endl;
}

이걸 이해하려면 C++ 의 Type System 을 이해 하면 간단합니다.

첫째 : &Some::func 는 Some의 Member function 으로서 void (Some::*foo)(void) 라는 Member Function Pointer Type을 가집니다.

둘째 : cout 은 stdout 을 대상으로 하는 출력 객체 이며 « 연산자에 대해 여러가지 type 으로 overloading 되어 있습니다. 실제 코드를 보면 bool, ushort ,short, int, uint, long, ulong, longlong, ulonglong, float, double, long double, void, char, whcar_t, char wchar_t* 에 대해 overloading 되어 있는것을 확인 할 수 있습니다(visual c++ 기준 ostream 헤더). string / wstring 에 대한 overloading 은 ostream 측에서 제공하는 것이 아니라 string 쪽에서 추가로 지원 하는 것이라 ostream 의 일부라 보기는 어렵습니다.

셋째 : overloading 된 함수를 호출 할 경우 완전히 매칭 되는 타입의 함수를 호출합니다.

하지만 내가 방금 만든 Class 의 MemberFunction Type 이 미리 준비 되어 있을리가 없겠죠

넷째 : 완전히 매칭되는 함수가 없을 경우 변환 가능한 가장 “적절한” type 의 함수로 호출합니다.

자 그럼 이제 어떤 type으로 캐스팅이 가능하냐를 봐야 하는데… 일단 정수형과 부동소수점은 딱 봐도 안될 것 같습니다.

문자 / 문자열포인터 역시 가지고 있는것이 string 일리가 없으므로 그렇게 암시적으로 캐스팅 하면 AccessVoliation 의 지름길일 겁니다.

그럼 void* 는 어떨까요? 임의의 pointer를 가지는 type 이지만 Member Function Pointer 만큼은 예외입니다.

void*는 일반적인 포인터 사이즈 이지만 Member Function Pointer 는 그렇지 않습니다. 상속을 어떤 구조로 어떻게 받느냐에 따라 크기가 변하지요

간단한 예로

1
cout << sizeof(&iostream::flush);

를 실행시켜 보면 VC++ x64 기준 16Byte가 나오는 것을 볼 수 있습니다. 경우에 따라 크기가 달라지는 이 시스템은 C++만의 독특한 구현이며 이것이 std::function 이 나오기 전까지 임의의 member function 을 가지는 변수를 선언 할 수 없었던 이유이기도 합니다. 그래서 void* 도 아웃이네요

자 그럼 bool 하나만 남았는데… 그럼 Member Function Pointer 가 bool로 캐스팅 될 수 있을까요? 정답은 가능하다 입니다.

어렵게 생각 하실 것 없습니다. 그동안 Function Pointer 를 다루셧던 분들은 아래와 같은 코드를 보거나 혹은 사용해 보셨을 것입니다

1
2
3
if(ptr) {
   //dosomething
}

이런식으로 ptr 이 nullptr 이 아닌지 체크 하여 사용하는 코드가 가능햇죠. 이 말은 반대로 말하면 ptr이 if 의 조건 이 될 수 있다는 것이며 if 의 조건이 될 수 있다는 말은 C++ 에서는 bool 로 캐스팅 가능 하다는 이야기가 됩니다.

C++ 표준 라이브러리의 많은 식도 이런식으로 bool 캐스팅을 가능하게 하여 구현했는데 shared_ptr 같은 포인터를 랩핑하는 객체가 대표적입니다. 특정 객체의 사용 가능 여부를 bool 로 캐스팅 하여 체크한다는 아주 단순한 방법을 이용 할 수 있게 하는거지요. 이런 기법을 C++ Safe Bool Idiom 이라고 부릅니다. Classic C++ 에서는 구현이 상당히 복잡했지만 C++11 에 와서는 특히 간단히 구현할 수 있게 되어 더욱 많이 쓰게 되는 방법이 되었지요.

자 그럼 Member Function Pointer 가 bool로 캐스팅이 될 수 있다는것은 알았습니다. 그리고 당연히 변수가 아니라 존재하는 Function 의 Pointer를 가져온 것이므로 nullptr 이 아닐것이 확실합니다. 결과적으로 cout « true 의 결과물인 1이 출력 되는 거겠구요.

그럼 위 코드에 간단한 함수를 하나 추가 해서 정말 그러한지 테스트를 해 봅시다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void foo(void* v)
{
    cout << "ptr";
}
 
void foo(bool v)
{
    cout << "bool";
}
 
void foo(int v)
{
    cout << "int";
}
 
void foo(double v)
{
    cout << "double";
}
 
int main()
{
    foo(&Some::func);
}

결과물은 당연히 bool이 나오게 됩니다.

참고자료 : C++ Idioms/Safe bool https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool

This post is licensed under CC BY 4.0 by the author.