codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.
멤버 함수의 호출 원리
class Point
{
int x = 0, y = 0;
public:
void set(int a, int b)
{
x = a;
y = b;
}
static void foo(int a) // void foo (int a)
{
x = a; // this-> x = a; 번경해야하는데... this가 없어서 error
// 따라서 static에서는 멤버변수를 사용할 수 없는 문법이 나오는 것임
}
}
int main()
{
Point::foo(10); // push 10
// call Point::foo
Point p1;
Point p2;
p1.set(10, 20); // 원리를 생각해 봅시다.
}
멤버함수는 객체마다(p1, p2) 만들어지는 것이 아니라 하나밖에 없음
p1.set일 때 어떤 객체의 set 인지 어떻게 알 수 있을까?
p1.set(10, 20) 을 보면 인자가 2개 밖에 없어보이지만 컴파일 시
- 컴파일러가 set(&p1, 10, 20) 으로 변경해줌
- 따라서 set(Point* const this, int a, int b)
- x = a; -> this->x = a;
- y = b; -> this->y = b;
set(&p1, 10, 20) 을 어셈블리로 나타내면
push 20
push 10
lea ecx, &p1 rcx, &p1 // 객체 주소는 레지스터로
call Point::set
위와 같이 호출되는 과정을 this call이라함
cl this1.cpp /FAs => this1.asm 어셈블리어 만들어짐
1.멤버함수는 1번째 인자로 객체의 주소(this)가 추가된다.
2.static 멤버함수는 객체의 주소가 추가되지 않는다.
멤버 함수 포인터
class Dialog
{
public:
void Close() {}
}
void foo() {}
int main()
{
void(*f1)() = &foo;
// void(*f2)() = &Dialog::Close;
void(Dialog::*f3)() = &Dialog::Close; // ok.. 멤버 함수 포인터.
Dialog dlg;
// dlg.f3(); // dlg.Close()의 의미.. 하지만.. f3이라는 멤버를 찾게된다. - error
// dlg.*f3(); // ".*" : pointer to member operator -> error. 연산자 우선순위 문제.
(dlg.*f3)(); // ok.. dlg.Close(); -> 함수포인터를 사용해 멤버함수 호출
}
void(*f2)() = &Dialog::Close; // error. this 가 추가되는 함수.
핵심 1. 일반 함수 포인터에 멤버함수의 주소를 담을 수 없다.
핵심 2. 일반 함수 포인터에 static 멤버함수의 주소를 담을 수 있다.
f3(); 으로 호출 가능?
-> error. 객체(this)가 없다.
핵심 3. 멤버 함수 포인터 모양과 사용법. “.” (p->f3)()
Thread class 만들기
#include<iostream>
#include<windows.h>
using namespace std;
DWORD __stdcall foo(void* p)
{
return 0;
}
int main()
{
CreateThread(0, 0,
foo, // 스레드로 수행할 함수
(void*)"A", // 스레드 함수로 보낼 인자
0, 0);
}
빌드하는법 VC: cl this3.cpp /nologo /EHsc
위의 코드는 C 기반임. 아래 Class로 점차 바꾸어감
#include<iostream>
#include<windows.h>
class Thread
{
public:
virtual void Main() {}
};
class MyThread : public Thread
{
public:
void run()
{
CreateThread(0, 0, threadMain, 0, 0, 0);
}
DWORD __stdcall threadMain(void* p)
{
Main(); // 가상함수 호출
return 0;
}
virtual void Main() {cout << "스레드 작업" << endl;}
};
int main()
{
MyThread t;
t.run(); // 이 순간 스레드가 생성되어서 가상함수 Main()을 실행하여야 한다.
getchar();
}
위의 코드 컴파일 시 에러 발생
threadMain this 가 없어야 함!
CreateThread 에 넘어가는 함수는 반드시 void* 하나여야하는데 static 이 없으니까 에러발생함
#include<iostream>
#include<windows.h>
class Thread
{
public:
virtual void Main() {}
};
class MyThread : public Thread
{
public:
void run()
{
CreateThread(0, 0, threadMain, 0, 0, 0);
}
// 1. 아래 함수는 반드시 static 함수 이어야 합니다.
static DWORD __stdcall threadMain(void* p)
{
Main(); // 가상함수 호출
return 0;
}
virtual void Main() {cout << "스레드 작업" << endl;}
};
int main()
{
MyThread t;
t.run(); // 이 순간 스레드가 생성되어서 가상함수 Main()을 실행하여야 한다.
getchar();
}
위의 코드 역시 다시 에러 발생
g++에서 without object 라고 Main(); 의 가상함수 호출에서 에러발생
threadMain은 static 이라 this 가 없는데 Main은 this 가 있음
Main(); 은 this->Main() => Main(this)로 변해야 한다.
그러나 static 함수는 this 가 없어 객체 없이 부르려고 한다는 에러메시지가 나오고 있는 것!
void run()은 static 이 아니므로 this가 있음
CreateThread는 4번째 파라미터에 인자로 보낼 수 있음 -> this를 보내 threadMain에서 casting 시킴!
#include<iostream>
#include<windows.h>
class Thread
{
public:
virtual void Main() {}
};
class MyThread : public Thread
{
public:
void run()
{
CreateThread(0, 0, threadMain, (void*)this, 0, 0);
}
// 1. 아래 함수는 반드시 static 함수 이어야 합니다.
// 2. 아래 함수는 this가 없다. 그래서 함수 인자로 this를 전달하는 기술.
static DWORD __stdcall threadMain(void* p)
{
Thread* const self = static_cast<Thread*>(p);
self->Main(); // Main(self)
// Main(); // 가상함수 호출
return 0;
}
virtual void Main() {cout << "스레드 작업" << endl;}
};
int main()
{
MyThread t;
t.run(); // 이 순간 스레드가 생성되어서 가상함수 Main()을 실행하여야 한다.
getchar();
}
this map 예제
#include<iostream>
#include "ecourse.hpp"
using namespace std;
using namespace ecourse;
void foo(int id)
{
cout << "foo : " << id << endl;
}
int main()
{
int n1 = ec_set_timer(500, foo); // 500ms 마다 foo 호출
int n2 = ec_set_timer(1000, foo); // 1000ms 마다 foo 호출
ec_process_message();
}
위의 코드 객체지향으로~
#include<iostream>
#include "ecourse.hpp"
using namespace std;
using namespace ecourse;
// 타이머 개념을 사용해서 Clock 클래스 만들기.
class Clock
{
string name;
public:
Clock(string n) : name(n) {}
void start(int ms)
{
int id = ec_set_timer(ms, timerHandler);
}
void timerHandler(int id)
{
cout << name << endl;
}
};
int main()
{
Clock c1("A");
Clock c2("\tB");
c1.start(1000);
c2.start(1000);
ec_process_message();
}
위의 코드 컴파일 시 에러!
timerHandler 가 멤버함수 이므로 this가 있어야함(인자 하나를 요구하는데 인자 2개 짜리임)
#include<iostream>
#include "ecourse.hpp"
using namespace std;
using namespace ecourse;
// 타이머 개념을 사용해서 Clock 클래스 만들기.
class Clock
{
string name;
public:
Clock(string n) : name(n) {}
void start(int ms)
{
int id = ec_set_timer(ms, timerHandler);
}
// 핵심 1. 아래 함수는 반드시 static 멤버 이어야 합니다.
static void timerHandler(int id)
{
cout << name << endl; // this->name
}
};
int main()
{
Clock c1("A");
Clock c2("\tB");
c1.start(1000);
c2.start(1000);
ec_process_message();
}
static 으로 했지만 name 은 여전히 불가
this가 있는 함수와 없는 함수의 공통요소를 뽑으면 id
id를 키로 가지는 data구조를 만들어 해결
#include<iostream>
#include "ecourse.hpp"
using namespace std;
using namespace ecourse;
// 타이머 개념을 사용해서 Clock 클래스 만들기.
#include<map>
class Clock
{
string name;
static map<int, Clock*> this_map;
public:
Clock(string n) : name(n) {}
void start(int ms)
{
int id = ec_set_timer(ms, timerHandler);
this_map[id] = this;
}
// 핵심 1. 아래 함수는 반드시 static 멤버 이어야 합니다.
static void timerHandler(int id)
{
Clock* const self = this_map[id];
// cout << name << endl; // this->name
cout << self->name << endl;
}
};
map<int, Clock*> Clock::this_map;
int main()
{
Clock c1("A");
Clock c2("\tB");
c1.start(1000);
c2.start(1000);
ec_process_message();
}
this를 자료구조에 넣었다가 static 에서 다시 꺼내는 기법이 많이쓰이는 기법 중
Callback 함수와 this
OS가 windows, linux, ios, android 던 1차 API는 C 언어가 있고 이 위에 2차 API로 C++언어, 객체지향 언어들이 있음
C언어 에서 어떤 함수가 다른함수를 인자로 가질 수 있음(CreateThread, SetTime)
-> 이런 것을 callback 함수라 함
이런 함수들은 2차 API에서 반드시 static 멤버함수여야함(일반 멤버함수는 this가 추가되어 문제가됨)
static 으로 가면 this를 사용할 수 없음
Callback 함수를 static member function로 만들 때 this를 사용하는 방법
- callback 함수의 인자로 this를 전달
- this를 자료구조(map)에 보관해서 사용