프로그램/C,C++

[C++ 기본 3] 3. this

naudhizb 2022. 4. 4. 16:14
반응형

객체의 멤버 함수나 멤버 데이터에 접근하려면 클래스 내부에서 각 함수나 변수의 이름을 사용하면 되었다. 사실은 이 경우 실 동작은 컴파일러가 직접 함수 선언에 대하여 내부적으로 this라는 인자를 생성하여 각 객체별로 함수나 변수가 동작하도록 변경하여 동작시키는 구조이다.

// 3_this1.cpp      126 page ~
#include <iostream>

class Point
{
    int x, y;
public:
    void set(int a, int b) // set(Point* this, int a, int b)
    {
        x = a;    // this->x = a;
        y = b;    // this->y = b;  처럼 컴파일 됩니다.

        // this : 현재 멤버 함수를 호출할때 사용한 객체의 주소
        std::cout << this << std::endl;
    }
};
int main()
{
    Point p1;
    Point p2;
    std::cout << "p1 : " << &p1 << std::endl;
    std::cout << "p2 : " << &p2 << std::endl;
    p1.set(10, 20); // set(&p1, 10, 20) 처럼 컴파일 됩니다.
                    // push 20
                    // push    10   실제 인자들.
                    // mov  ecx , &p1   객체 주소는 레지스터로
                    // call Point::set
}

통상적으로는 this를 쓰는 경우에 따로 생각하지 않아도 된다. 왜냐하면 컴파일러가 알아서 찾아주기 때문이다. 하지만 꼭 this 키워드를 써야하는 경우가 있다. 바로 변수명이 충돌하는 경우이다.

// 3_this1.cpp      126 page ~
#include <iostream>

class Point
{
    int x, y;
public:
    void set(int x, int y) 
    {
        // this 활용 1. 이름이 충돌날때 멤버에 접근하고 싶을때
        this->x = x;    
        this->y = y;
    }
    // 활용 2. this를 반환하는 함수
    //        함수를 연속적으로 호출가능
    Point* foo() { return this; }
    //Point  goo() { return *this; } // *this가 아니라 
                                    // 복사본(임시객체) 반환
    Point&  goo() { return *this; } // ok.
            // 자신을 값으로 반환할때는 반드시 참조반환 해야합니다.

};
int main()
{
    Point p1;
    Point p2;
    p1.set(10, 20);

    p1.foo()->foo()->foo();

    p1.goo().goo().goo(); // cout << "A" << "B" << "C" 의 원리
}

인자로 받은 변수와 클래스의 멤버 변수 이름 두 가지가 모두 같은 경우에는 인자의 이름이 우선한다. 때문에 멤버 변수에 대하여 this->x와 같이 명시적으로 변수이름을 써 값을 지정하여야 한다.

객체를 반환하는 함수 작성 시 객체를 반환하는지, 객체 레퍼런스를 반환하는지에 따라 return by value, return by reference가 정해지므로 주의하도록 한다.

#include <iostream>

class Car
{
public:
    // 다음중 에러를 모두 고르세요
    void foo()        { std::cout << this << std::endl; }// 1
    static void goo() 
    {
        std::cout << this << std::endl; // error.
            // static 멤버 함수에서는 this를 사용할수 없다.
            // 객체 없이 호출할수 있기 때문에
    }// 2
};

int main()
{
}

this의 경우 당연히 인스턴스에 의존하므로 정적 함수에서는 사용할 수 없다.

// 3_this4
// 1. 일반 함수 포인터에 멤버 함수 주소를 담을수 없다.
// 2. 일반 함수 포인터에 static 멤버 함수 주소를 담을수 있다.

class Dialog
{
public:
    void Close() {}
    static void Close2() {}
};
void foo() {}

int main()
{
    void(*f1)() = &foo; // ok.
    void(*f2)() = &Dialog::Close; // 될까요 ?
            // error. 멤버 함수는 this가 전달됩니다

    void(*f3)() = &Dialog::Close2; // ok.
}

멤버 함수의 경우 내부적으로 자신의 객체인 this를 인자로 자동 추가하기 때문에 코드상으로 보이는 인자가 컴파일러가 인식하는 인자와 다르다.
정적 함수의 경우 인스턴스 this가 추가되지 않기 때문에 함수포인터를 이용하여 함수 지정이 가능하다. .

프로그램을 전역 함수나 변수를 배제하고 객체 지향으로 작성하게 되면 자동적으로 this인자가 추가된다. 하지만 특정 함수의 경우 인자가 고정될 수 있다. 이 경우 인자가 달라 동작 오류가 발생하는 것을 없애기 위하여 static 함수를 선언하여야 되는 경우가 생긴다.(대표적으로 thread와 callback이 있다)

#include <iostream>
#include <Windows.h> // windows 용 함수들..

/*
// win32 api 에서 스레드 함수 모양
DWORD __stdcall foo(void* arg)
{
    return 0;
}
int main()
{
    CreateThread(0, 0, &foo, "A", 0, 0); // 스레드 생성함수
                // linux : pthread_create()
}
*/
// 스레드 개념을 객체지향에서 활용해 봅시다.
class Thread
{
    int data = 0;
    
public:
    void run() { CreateThread(0, 0, &threadMain, this, 0, 0); }

    // 핵심 1. 아래 함수가 static 멤버 함수로 만드는 이유를
    //         정확히 알아야 합니다.
private:
    static DWORD __stdcall threadMain(void* arg)
    {
        //data = 20; // this->data = 20  로변해야 하는데
                    // this 가 없다.

        Thread* self = static_cast<Thread*>(arg);

        // 이제 self가 this입니다.
        // self를 사용하면 모든 멤버에 접근 가능합니다.
        self->data = 10;

        return 0;
    }
};

int main()
{
    Thread t;
    t.run(); // 이순간 스레드가 생성되어야 합니다.
    
}

// 핵심.
// C언어에서의 callback 함수.  setTimer(1second, &foo)
// 는 반드시 함수 모양이 정해져 있습니다.

// C++로 디자인할때  callback 함수는 static 멤버 함수이어야합니다.

// static 멤버 함수에서는 this가 없어서 멤버 데이타에 접근이 안됩니다.
// this를 사용하게 하는 다양한 기법이 있습니다.

threadMain 함수를 전역 함수로 선언하였다면 static으로 선언하지 않아도 되나 객체지향적인 코드가 아니라고 볼수도 있다. 객체 지향 설계를 위하여 threadMain함수를 객체 안으로 넣기 위해서는 static 함수로 선언하여 this 인자가 더해지는 것을 막아야 한다.
두 경우 모두에 대하여 자동으로 인자로 this가 넘어가지 않으므로 threadMain은 멤버 함수나 변수를 이용할 수 없다.
??? 이상하지 않은가? context에 아예 접근 불가능한 객체 내의 함수라니,,
thread의 경우 인자로 void 포인터를 받게 되어 있고, 이러한 패턴을 이용하여 this를 전달 한 다음, 내부에서 인스턴스 포인트로 변환하여 사용한다.

callback의 경우에도 마찬가지이다.

반응형