I'm FanJae.

[C++ Intermediate] Member Function Pointer 본문

C++/Intermediate

[C++ Intermediate] Member Function Pointer

FanJae 2024. 9. 23. 22:36

1. Member Function Pointer

 

1-1. 함수 포인터와 멤버 함수

class X
{
public:
          void mf1(int a) {} // void mf1(X* this, int a)
   static void mf2(int a) {}
};

void foo(int a) {}

int main()
{
    void(*f1)(int) = &foo;  // ok
//  void(*f2)(int) = &x::mf1; // error;
    void(*f3)(int) = &x::mf2; // ok
    
    void(X::*f2)(int) = &X::mf1; // ok
}

- 일반 함수 포인터에 멤버 함수의 주소를 담을 수 없다.

- 일반 함수 포인터에 static 멤버 함수는 담을 수 있다.

 

※ 일반 함수 포인터와 멤버 함수 포인터

void(*f1)(int) = &foo;
void(X::*f2)(int) = &X::mf1;

 

※ 함수의 주소를 얻는 방법

foo ok. 함수 이름은 함수 주소로 암시적 변환 가능
&foo ok.
X::mf1 error. 멤버함수는 반드시 주소 연산자(&) 필요
&X::mf1 ok.

 

1-2. pointer to member 연산자

class X
{
public:
          void mf1(int a) {} // void mf1(X* this, int a)
   static void mf2(int a) {}
};

void foo(int a) {}

int main()
{
    void(*f1)(int) = &foo;  // ok
//  void(*f2)(int) = &x::mf1; // error;
    void(*f3)(int) = &x::mf2; // ok
    
    void(X::*f2)(int) = &X::mf1; // ok
    
    f1(10); // ok. 일반함수 포인터로 함수 호출.
//  f2(10); // error. 객체가 필요하다.

    X obj;
//  obj.f2(10); // error. f2라는 멤버를 찾게 된다.
    
    // pointer to member 연산자 사용
//    obj.*f2(10); // .*을 pointer to member 연산자라고 한다.
    // 다만 괄호 연산자의 우선순위가 높기 때문에 f2(10)을 먼저 한다.
    // 따라서 이것도 연산자 우선순위 문제로 에러가 발생한다.
    
    (obj.*f2)(10); // ok
       
}

 

※ 멤버 함수 포인터를 사용해서 멤버 함수를 호출하는 방법

f2(10); error.
멤버 함수를 호출하려면 객체가 필요하다.
obj.f2(10); error.
obj 객체에서 f2라는 멤버가 있는지 검색.
obj.*f2(10); error.
() 연산자가 .* 연산자보다 우선순위가 높다.
(obj.*f2)(10); ok

 

Pointer To Member Operator

- 멤버에 대한 포인터(멤버 함수 포인터, 멤버 변수 포인터)를 사용해서 멤버에 접근할 때 사용하는 연산자

.* (obj.*f2)(10);
->* (pobj->*f2)(10); pobj는 obj를 가리키는 포인터

 

1-3. 일반 함수 포인터와 멤버 함수 포인터의 차이

#include <functional>

class X
{
public:
    void mf1(int a) {}
};
void foo(int a) {}

int main()
{
    X obj;
    
    void(*f1)(int) = &foo;
    void(X::*f2)(int) = &X::mf1;
    
    f1(10);        // 일반 함수 포인터 사용
    (obj.*f2)(10); // 멤버 함수 포인터 사용
}

- 함수 포인터를 사용해서 함수를 호출하는 방법이 다르다.

- STL 에서는 몇가지 도구가 제공된다.

 

std::invoke ( C++17 )

- funtional 헤더

- 일반 함수 포인터와 멤버 함수 포인터(정확히는 callable object)를 동일한 방법으로 사용(호출)할 수 있다.

std::invoke(일반 함수 포인터, 함수 인자들...);
std::invoke(멤버 함수 포인터, 객체, 함수 인자들...);
std::invoke(멤버 함수 포인터, &객체, 함수 인자들...);
#include <functional>

class X
{
public:
    void mf1(int a) {}
};
void foo(int a) {}

int main()
{
    std::invoke(f1, 10);       // f1(10)
    std::invoke(f2, obj, 10);
    std::invoke(f2, &obj, 10);
    
    auto f3 = std::mem_fn(&X::mf1);
    f3(obj, 10);
    f3(&obj, 10);
}

 

 std::invoke ( C++11 )

- functional 헤더

- 멤버 함수의 주소를 인자로 받아서 함수 주소를 담은 래퍼 객체 반환.

void(X::*f2)(int) = &X::mf1;
(obj.*f2)(10)
auto f3 = std::mem_fn(&X::mf1);
f3(obj, 10);
f3(&obj, 10);

2. Pointer to Member data

#include <iostream>
struct Point
{
    int x;
    int y;
};

int main()
{
    int num = 0;
    
    int* p1 = &num;
    
    int Point::*p2 = &Point::y;
 
}

- 멤버 데이터를 가리키는 포인터이다.

- 이 예제에서 다음과 같은 생각을 해볼 수 있다. p1은 num 변수의 메모리 주소를 가지고 있다.

- p2는 무엇을 담을까?

※ 대부분의 컴파일러에서 p2는 Point 구조체 안에서 y의 offset(4)을 담고 있다. - 공식 표준은 아니며, 컴파일러 마다 이는 다르다.

 

#include <iostream>
struct Point
{
    int x;
    int y;
};

int main()
{
    int num = 0;
    
    int* p1 = &num;
    
    int Point::*p2 = &Point::y; // 몇번째 offset인지 (정확히는 아님)
 
    *p1 = 10; // ok
    
    Point pt;
    pt.*p2 = 10; // pt.y = 10
                 // *((char *)&pt + p2) = 10;
               
    std::cout << p2 << std::endl; // 1
    printf("%p\n",p2); // 4
}

 

#include <iostream>
#include <functional>

struct Point
{
    int x;
    int y;
};

int main()
{
    int Point::*p = &point::y;
    
    Point obj;
    
    obj.*p = 10; // obj.y = 10
    (&obj)->*p = 10;
    
    std::invoke(p, obj) = 20; // obj.y = 20
    int n = std::invoke(p, obj);
    
    std::cout << n << std::endl;
    std::cout << obj.y << std::endl;
}

 

※ 멤버 데이터를 가리키는 포인터(p)를 사용해서 객체의 멤버 데이터에 접근하는 2가지 방법

- 객체 obj 생성을 한 이후 다음과 같이 사용 가능하다.

.*, ->* 연산자
(pointer to member)
obj.*p = 10;
(&obj)->*p = 10;
std::invoke  std::invoke(p, obj) = 10;
int n = std::invoke(p, obj);

 

- invoke에서 첫번째 인자에 들어가는 값은 멤버 함수의 포인터 뿐 아니라, 멤버 데이터의 포인터도 넣을 수 있다.

 

※ callable type

- std::invoke()를 사용할 수 있는 타입

- 함수, 함수 포인터, 멤버 함수 포인터, 멤버 데이터 포인터, 람다 표현식이 만드는 타입, operator() 함수를 제공하는 타입

Comments