Hoon222y

[1219-1] 정적 멤버 - this와 thiscall 본문

코딩/교육

[1219-1] 정적 멤버 - this와 thiscall

hoon222y 2018. 12. 19. 19:07

this : 객체 자신을 가리키는 포인터 상수를 의미한다. this 의 경우 멤버 함수 내에서만 사용이 가능하다. this 인수는 함수를 호출한 객체의 포인터 이며 모든 문장 앞에 this->가 암시적으로 적용된다. 

this 를 사용하는 이유는 

1. 변수가 해당 객체의 멤버임을 알리기 위함

2. 멤버 함수를 연속적으로 호출하기 위한 2가지 이유가 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;
class Int32{
public:
    int var;            //밖에서 접근할 수 있도록 하기위함
 
    void set(int var){
        var = var;      //여기에서 var = 0;로 끝나는것이다. 즉 가장 가까운 곳에 할당이 됨.
                        //해결책은 멤버 데이터의 이름을 바꾸면 된다.
                        //매개변수와 멤버 데이터의 이름이 같아지게 되는경우가 있는데 이문제를 해결하기 위해 this를 사용한다.
                        //즉
        this->var = var;//이런식으로 하면 된다.
        //this = 0;     //는 에러이다. 왜나면 this 자체가 상수이기 때문
    }
};
cs


 해당 코드에서 주의 할점이 있다면 this 자체가 포인터 상수이기 때문에 this = 0 처럼 따로 값을 할당하는 것은 에러가 발생한다는 점이다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Car{
public:
    //this 사용 이유
    // 2. 멤버 함수를 연속적으로 호출하기 위함
    Car* go(){
        cout << "Go!" << endl;
        return this;
    }
    
};
 
class Person{
    char name[32];
    int age;
public:
    Person* set_name(const char* name){
        strcpy(this->name, name);
        return this;
    }
    Person* set_age(int age){
        this->age = age;
        return this;
    }
    void update(){
        cout << "저장 완료" << endl;
    }
    
};
 
int main(){
    //Car c;
    //c.go()-> go() -> go();
    
    Person p;
    //p.set_age("dd");
    //p.set_age(11);
    //이런식으로 일일이 할 필요 없다.
    p.set_name("hoon")->set_age(10)->update();
    //이런식으로 Person* 타입으로 하고 return this 하면 멤버함수를 연속적으로 호출 할 수 있다.
}
cs


 해당코드를 살펴보면 this 를 사용하는 두번째 이유가 나온다. 보통의 경우는 Person p 를 선언하고, 각각의 함수를 통하여 일일이 초기값을 정의하는 경우가 있다. 하지만 이러한 번거로움을 덜기 위하여 void set_XX가 아니라 해당 클래스명 *형을 return 타입으로 선언하고 return this를 해주는 방식을 생각하면 된다. 

 여기서 내가 모르는 부분인 16번째 줄에서 입력되어진 char 배열을 해당 객체에 strcpy(this->name , name)과 입력되는 인자가 (const char* name)인 것을 확인 하자. 


이렇게 동작하는것을 thiscall이라고 한다. 코드를 보면서 이해해 보도록 하자 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
 
void foo(){}
 
class Int32{
public:
    int var;                            //지역변수는 stack 전역변수는 data 영역
                                        //동적할당은 heap에 , 함수는 text영역에 저장되어 있다.
                                        //함수는 text영역에 있는데 빌드되면 변경되지 않기 떄문에
    void set(int var){                  //  void set(Int32* const this, int var){
        var = var;                      //      this->var = var;
    }                                   //  } 로 들어가는 것이다.
};
 
int main(){
    foo();                              //__cdecl 방식으로 호출되는것이고 (전역 함수이기 때문에)
    Int32 i;
    cout << sizeof i << endl;           //출력결과는 4로 나온다.
    i.set(0);                           //set(&i,0); 로 들어가는 것이다. //이게 thiscall 방식이다.
    cout << i.var << endl;
}
cs


 전역 함수처럼 아무런 인자 없이 호출되는 방식을 __cdecl 방식이라고 한다. 하지만 클래스 내에서 호출의 경우는 조금 다르다.

 그전에 class의 크기에 대해서 먼저 말을 하자면 sizeof i 의 결과값이 4임을 볼 수 있다. 이는 지역변수나 전역변수, 동적할당 같은 경우에는 각각 stack, data 영역, heap 영역에 저장이 되지만, 함수는 text 영역에 저장이 되어있는데 이는 따로 객체의 크기에 영향을 끼치지 않기 때문이다. 예를들어 void set 함수 내에 int a=4;

라고 선언했다고 가정하자. 18~19줄 사이에 i.set(0) 을 실행 했다고 가정한다면 sizeof i의 크기값이 8이 되지 않을까 하지만 함수가 종료되면서 해당 a라는 변수는 사라지기 때문에 그대로 4라는 값이 나오게 된다. 


 이번에는 thiscall에 대해서 이해하기 위해 void set(int var)을 살펴보자. 

Int32 i라는 객체를 생성하고 20번째줄에서 i.set(0); 이라는 코드를 수행했다. 이때는 컴파일러 자체 내에서 set(&i,0); 이라고 자동으로 변환하고, 전달을 하는것이다. 이것이 바로 thiscall 방식이다. 

 class내에서 void set 함수의 경우 인식을 할 때는 

1
2
3
void set(Int32* const thisint var){
    this->var = var;
}
cs

 과 같이 받아들이게 된다. 

이때 인자로 들어가는 것은 클래스 명* const this 가 들어가는 것을 알아두자. 

Comments