class에 대한 얘기(2)

2021. 11. 24. 19:45c,c++ 기본/next step

320x100

안녕하세요 대학생 개발자입니다.

 

이번글에서는 저번글에 이어서 클래스에 대한 정리를 좀더 해보려고합니다.

 

저번글에서는 기본 생성자와 소멸자에 대한 얘기를 해봤습니다.

그럼 다음으로 기본 복사생성자에 대한 얘기를 해보겠습니다.

 

우리가 코딩을할때 분명 복사생성자를 만든 기억이 없음에도

 

 

이런 복사생성자 호출이 가능해집니다.

그렇다는 말은 컴파일러가 복사생성자를 만들어 준다는 의미겠죠.

 

네. 여기까지가 c++을 훌륭히 배워오신 분들이 가지고 있는 지식입니다.

하지만 조금만 더 깊게 들어가보겠습니다.

어셈블리를 통해서 기본 복사생성자라는 것을 자세히 보면 그냥 memcpy호출과 동일합니다.

네... 이게 기본 복사생성자의 실체입니다. 그냥 복사를 드르르르륵 하고 있는 상황인겁니다.

아... 그럼 memcpy를 한번 봐야겠죠? 동일하다고 말씀드렸으니...

음... 동일해보이죠? Destination = t2, Source = t1

이걸 놓고보면 그냥 바이트씩 복사를 한다라는 점에서 완벽하게 동일한 코드가 뽑히게됩니다.

네... 그렇습니다 c++에서 만들어주는 기본 복사생성자의 의미는 그냥 memcpy였던겁니다.

저는 그래서 사실 기본 복사생성자는 그냥 memcpy다 라고 말하는 편입니다.

 

네 그리고 추가로 복사생성자에서 말해볼 것이있는데요.

복사생성자는 파라미터로 주소를 받습니다.

여기서 아마 엥 무슨개소리야 레프런스를 받는거잖아 라고 하시는 분들이 계실겁니다...

이전에 제가 올린글중에 레프런스 변수에 대해 언급하는 곳이 있습니다.

c언어가 기반이 되는 c++에서 변수를 조작할 수 있는 방법은 두가지밖에없습니다.

1. 그 변수 이름을 이용해 직접건드린다.

2. 주소를 이용해서 접근해 간접적으로 건드린다.

 

그럼 서로 다른 스택에 존재하는(생성자와 메인함수처럼) 레프런스 변수는 어떻게 접근할까요? 그변수에는 접근이 불가능할텐데요 c문법상 말이죠.

그렇기 때문에 남은 하나는 주소를 이용해 접근한다는 방법밖에 없습니다. 그리고 실제로도 주소를 전달하고 포인터와 동일하게 행동하는 모습을 제가 직접 보여드렸었죠.

 

다시 본론으로 돌아가서 계속 얘기를 해본다면. 주소를 받습니다. 왜 주소를 받을까요?

아마 들으면 너무 당연한 얘기를 하는거라 웃음이 나올겁니다.

값을 받게되면 그순간 복사생성자가 또 호출됩니다. 그럼 복사생성자에서는 또 복사가 일어나겠죠...

이런 재귀에 빠지게 되는데요... 물론 vs를 쓰고 있어서 친절히 알려준다만 다른 컴파일러는 알려주나요? ㅋㅋㅋ 제가 windows vs19 환경에서만 너무 친숙하다 보니까 다른건 잘모르고있습니다.

 

네.. 그래서 제가 하고싶은말은 언어상에서 친근하게 복사생성자가 존재해 라고 알려주긴 하지만... 그행동은 그저 복사인겁니다.

explicit의 경우에는 묵시적 형변환을 통해서 생성자 호출을 하지 말라는 의미입니다. 이건... 그냥 크게 중요하게 생각하고 있지는 않습니다만 모르고 넘어가는건 좋지 않기떄문에 일단은 정리는 하고 넘어갑니다.

 

음... 그리고 나중에 또 정리를 할 것이지만... 가상함수가 포함된 상속관계의 클래스에서는 vftbl이라는 것이 생깁니다. virtual function table입니다. 이 vftbl을 정리해주는 것도 생성자의 행동입니다.

 

 

자... 여태껏 생성자와 소멸자에 대한 얘기를 했습니다

생성자와 소멸다는 반환타입이 없는 함수라고 하죠.

네 함수맞습니다. 근데 혹시 저처럼 이거 궁금하신 분 없었나요?

저는 함수를 호출할때 각 객체마다 함수의 코드 부분도 같이 생성되는걸까 하는 말도안되는 생각을 예전에 했었습니다.

네... 너무 말투에서 티가 났군요

클래스 맴버함수를 호출할때는 그냥 하나의 코드로 보내버립니다.

그러면 어떤 객체의 함수인지는 어떻게아는가요? 라고 물으신다면... 여기서 나오는게 this포인터의 존재입니다.

 

class Test
{
public:
    void Func()
    {
        printf("Func called\n");
    }
    int a;
};

이런코드가 존재합니다.

여기서 메인함수에서 Test 객체를 하나 선언후 Func를 호출한다면 어떻게될까요?

한번 어셈블리를 파보겠습니다.

네 rcx에다가 t1의 주소를 넣고 call을 합니다. 뭐 아무 이유없이 갑자기 rcx에 t1의 주소를 넣진 않았을겁니다.

즉 파라미터로 t1의 주소를 전달하고 있다는 것을 알 수 있습니다.

그럼 그때부터는 저 rcx에 담긴 t1이 this가 되는겁니다.

 

이때부터는 똑같습니다. 그냥 함수를 호출하는데 포인터를 전달했고 그 맴버변수에 접근한다고 한다면 포인터를 통해서 접근하는겁니다. 이게 this포인터의 의미입니다.

 

그럼 여기서 제가 또 장난기가 발동을하죠...

this가 널이라면 어떨까요?

this가 널이라면 당연히 안되야 될것 같습니다만... 안타깝게도 잘 작동합니다.

함수에 파라미터로 널포인터를 전달하는건 문제되는 일이 아니니까요.

함수에서 그 널포인터를 찔렀을때 문제가 되는거죠 ...

 

그렇습니다. 결국 nullptr을 전달하든 20을 전달하는 0x1234를 전달하든 어떤것이든...

this를 사용하지 않는다면 문제가되지 않습니다. 문제가 되는건 잘못된 포인터가 전달된것이 문제가 아니라 잘못된 포인터를 사용했을때 문제가 되는거니까요.

int main()
{
    Test* t1 = nullptr;
    t1->Func();
}

코드를 한번 수정해봤습니다. 당연히 되겠지만 되는것도 다시한번 보고넘어가는게 좋지않겠습니까.

네 뭐 문제없이 되었습니다. 당연한거였으니까요.

이런식으로 한다면 두개의 객체를 두고 분명 t1->Func()를 호출했지만 결과는 t2->Func()처럼 될수도 있겠죠 어차피 Test::Func()를 호출한거니까요 파라미터로 뭘 전달했느냐 차이지

class Test
{
public:
    void Func()
    {
        printf("Func called\n");
        a = 20;
    }
    int a;
};

int main()
{
    Test* t1 = nullptr;
    Test t2;
    t1->Func();
}

메모리뷰에서 t1->Func()에 전달된 파라미터값을 t2의 값으로 바꿔주겠습니다.

지금의 코드대로라면 당연히 잘못된 메모리를 찔렀기때문에 터져야 정상일겁니다.

하지만 중간에서 파라미터로 전달된 rcx의 값을 t2의 주소로 바꿔버렸습니다.

어셈블리는 위와같이 뽑혔고 mov dword ptr[rax], 14h 문장 직전에 rax값을 t2의 주소로 바꿔버렸습니다.

당연하게도 에러는 안났구요

return문을 만나고 메인함수로 돌아왔을때 t2의 값이 변경되어있는 것을 확인했습니다.

음... 좋습니다.

이런 장난을 치면서 실력이 늘어나는거죠뭐...

 

째뜬 맴버함수나 맴버 변수에 접근을 하려면 무조건 this가 필요하다는 것입니다.

 

단 static 함수의 경우에는 this가 전달이 되지 않습니다. 그러므로 맴버변수를 건드릴 수 가없죠.. static 맴버함수에서 static 맴버변수만 건드릴 수 있는 이유는 이때문입니다. this가 전달이 되지 않으니...어떻게 건드릴 방법이 존재하지 않는거죠.

 

뭐 this의 개념은 여기까지 정리해도 될 것 같습니다.

 

Temporary 객체, 임시객체라고하죠 이번 라인에서만 유효한 객체는 그 라인안에서 생성자와 소멸자 호출이 다이루어지고 다음라인부터는 문법상 사용이 불가능합니다. 하지만 실제 메모리는 스택에 쌓여있죠.

물론 const reference로 받으시면 그순간부터 그 객체의 생명은 더 연장이 됩니다. 그 const reference 변수의 스코프까지로요.

 

음... 그리고 friend입니다.

대부분 사람들이 friend선언을 거의 goto선언만큼 최악으로 여기시는 분들이 꽤나 있습니다...

friend는 필요하면 하는게 좋습니다... 특히 자기 라이브러리 안에서 사용할때는 더더욱이요.

자기 라이브러리에서 버퍼에 데이터를 넣으려고할때 Getter를 쓰시겠어요? 그냥 저같으면 friend해놓고 바로 포인터 찔러버립니다. 그게더 빠르니까요...

아 그리고 friend의 경우에는 friend A라면 내가 A를 허용해주는거지 A를 접근하겠다가 아닙니다.. 가끔이거 헷갈려 하시는 분들 꽤나있어서

friend A 하게되면 내가 A에게 오픈하겠다는 겁니다. 오해 하시면 안댑니다.ㅋㅋㅋ...

 

자 그럼 여기서 마무리 하겠습니다.

오늘도 긴글 읽어주신 여러분들께 감사합니다.

초반에는 글이좀 멋스럽고 나름 정돈이 되어있었던 것 같은데 갈수록 내용이 뒤죽박죽이고 그렇습니다...

A필기했다가 B필기했다가 다시 A필기했다가 이런게 많아서... 위아래로 왔다갔다하면서 글을 쓰고있다보니... 내용이 매끄럽지 않은점 양해 부탁드립니다.(물론 내가 보려고 정리한거긴하지만...)

째뜬 저는 다음에 더 좋은 정보로 돌아오겠습니다 아마 다음에는 가상함수, 추상화 이런거에 대해서 얘기를 하겠죠...

고럼 안녕히 계세요!

320x100

'c,c++ 기본 > next step' 카테고리의 다른 글

상속에 대한 얘기  (0) 2021.11.24
template와 class에 대한 얘기(1)  (3) 2021.11.23
new와 delete에 대한 얘기  (0) 2021.11.20
메모리와 c++에 관한 얘기  (0) 2021.11.14
DLL,LIB, FILEIO에 대한얘기  (0) 2021.11.13