본문 바로가기
프로그래밍 언어/C++

C++ 4단원 복습! (2편)

by 조엘 2020. 3. 15.

안녕하세요. 파피몬입니다.

 

이 복습 내용은 최호성 선생님의 "이것이 C++이다"를 공부하고 작성하였습니다. 

아직 모르는 게 많은 학생이라 오개념이 있을 수 있습니다. 친절하게 댓글로 알려주시면 좋은 공부 될 것 같습니다!!

4단원에서는 복사 생성자와 임시 객체에 대해서 배워봅니다. 1편, 2편으로 나누어져 있습니다.

 

Chapter 4. 복사 생성자와 임시 객체

 

이동 생성자를 공부할 차례입니다. 공부에 앞서 임시 객체와 r-value 참조에 대해 알고 시작해야 합니다.

 

7. 임시 객체를 알아보기 전 간단한 C언어 코드를 살펴보자.

#include<stdio.h>

int TestFunc()
{
    int nData = 10;
    return nData;
}

int main()
{
    int nResult = TestFunc();
    return 0;
}

 

이해하기 어려운 부분이 전혀 없는 코드지만 한 가지 의문이 생긴다. TestFunc()에서 리턴해준 nData는 지역변수인데 소멸되지 않고 대체 어떻게 저장되어 nResult에 저장되는 것일까? 

C언어에서는 EAX레지스터에 임시로 저장하여 결과를 옮겨준다고 한다. 임시 int(?)가 생겨버리는 것이다. 

 

이제 class를 위와 같은 방식으로 리턴해 준다고 생각해보자. 

//Class 선언부(CTestData) 및 기타사항 생략
//사용자 코드부터 작성
CTestData TestFunc(int nParam)
{
    CTestData a(nParam, "a");
    return a;
}

int main()
{
    CTestData b(5, "b");
    b = TestFunc(10);
    return 0;
}

마찬가지이다. TestFunc의 스코프 안에서 살아있던 a 인스턴스는 용케도 살아남아 main함수의 b 인스턴스에 대입 연산을 수행한다. 이 용케도 살아남는 과정이 "임시 객체" 인 것이다.

하지만, 정확히 말하면 원본이 살아남지는 않는다. a 인스턴스는 TestFunc() 함수가 종료될 때 소멸하며, 미리 만들어 놓은 이름도 없는 a 인스턴스를 복사한 임시 객체가 살아남는 것이다. 

이것을 "이름 없는 임시 객체" 라고 부른다. 이 "이름 없는 임시 객체" 의 복사 생성과 소멸은 모두 b = TestFunc(10); 이 한 줄에서 모두 일어난다. 대입을 해줄 수 있게 아무도 몰래 리턴 받는 객체를 생성해 주었다가 소멸시키는 것이다. 

 

그렇다면 b = TestFunc(10); TestFunc(10);으로 위의 코드를 바꿔보면 어떨까? 리턴받은 객체 아무곳에도 쓸 데 없으니 생성이 안되지 않을까? 안타깝지만 그럴일은 없다. 그냥 무조건 생성하더라 이 말이다.  

 

이름 없어 서러운 임시 객체에게 이름을 부여하는 방법이 있으니 그것이 바로 참조자이다. 정확히 말하면 별명을 부여하는 것이다. 이를 통해 임시 객체를 잠깐 반짝 했다가 사라지는 객체가 아닌 수명을 조절할 수 있는 객체로 만들 수 있다.

//Class 선언부(CTestData) 및 기타사항 생략
//사용자 코드부터 작성
CTestData TestFunc(int nParam)
{
    CTestData a(nParam, "a");
    return a;
}

int main()
{
    CTestData &rData = TestFunc(10);
    return 0;
}

이와 같다면 rData는 main 함수의 종료시 까지 살아남는다. 

 

여기서 질문! 첫번째 처럼 b에 리턴 값 넣는거나, 참조자로 임시객체에 이름 붙여주는거나 어차피 main함수 종료 까지 수명이 연장되는 것은 똑같지 않은가??

 

그건 맞으나, 문제는 메모리에 있다. 첫번째 예시는 총3개 ( a , b , 임시 객체 )의 클래스 인스턴스가, 두번째 예시는 총 2개 ( a , 임시 객체 )의 클래스 인스턴스가 생성되고 소멸한다. 만약 클래스 하나가 영상 데이터나 링크드 리스트같이 큰 메모리를 담당한다면 이는 성능 저하의 문제로 이어질 수 있다. 

 

 

8. r-value 참조는 말 그대로 r-value에 해당하는 값에 참조할 수 있는 것이다. 코드를 보면서 살펴보자. 

#include<iostream>
using namespace std;

void TestFunc(int &rParam)
{
    cout << "TestFunc(int &)" << endl;
}

void TestFunc(int &&rParam)
{
    cout << "TestFunc(int &&)" << endl;
}

int main()
{
    //3+4는 r-value
    TestFunc(3+4);
    return 0;
}

--------------------
TestFunc(int &&)

위와 같이 r-value를 참조할 수 있게 되었다. 이는 클래스에 적용할 때 요긴하게 쓰인다. 

 

 

9. 이동 생성자를 공부해보자. 앞서 배운 복사 생성자는 Deep Copy를 수행하기 위해 만들어 졌지만, 이동 생성자Shallow Copy를 수행하기 위해 필요하다. 위에서 설명한 임시 객체는 어차피 곧 사라질 객체이니, Shallow Copy를 통해 메모리를 최소한으로 사용한다는 것이 포인트이다. 

해당 코드의 주석에 어찌 작동하는지 적어 놓았습니다. 

#include<iostream>
using namespace std;

class CTestData
{
private:
	int *nData = nullptr;

public:
    //변환 생성자
	explicit CTestData(int nParam)
	{
		cout << "CTestData(int)" << endl;
		nData = new int;
		*nData = nParam;
	}
    
    //소멸자
	~CTestData()
	{
		cout << "~CTestData()" << endl;
		if (nData)
			delete nData;
	}
    
    //이동 생성자! 곧 사라질 원본 객체를 rhs에 받아 
    //이름 없는 임시 객체에 얕은 복사를 진행해 주고
    //원본 객체가 가르키는 것을 nullptr로 없애준다!
    //(만약 delete하면 임시 객체가 가르키는 것도 날라감)
	CTestData(CTestData &&rhs) : nData(rhs.nData)
	{
		cout << "CTestData(CTestData &&)" << endl;
		rhs.nData = nullptr;
	}
    
    //대입 연산자
    //여기서는 이름 없는 임시객체가 rhs_temp로 넘어오는 것을 볼 수 있다
	CTestData& operator=(const CTestData &rhs_temp){
		*(this->nData) = *(rhs_temp.nData);
		return *this;
	}
};

CTestData TestFunc(int nParam)
{
	CTestData a(nParam);
	return a;
}

int main()
{
	CTestData b(10);
	b = TestFunc(20);
	return 0;
}

결과는 다음과 같다

디버거를 통해 메모리를 하나하나 따라가 보았다. 코드대로 이동 연산자가 호출 되어 해당 원본 객체가 가르키는 포인터는 nullptr로 가르키는 것을 끊어버렸다. 마지막 대입 연산을 수행하기 전 메모리에 저장된 것들을 보니, 임시 객체가 rhs_temp에 넘어가 유일하게 a 인스턴스의 정보를 담고있는 것을 볼 수 있었다. 

대입 연산을 수행하기 전 메모리에 저장 사항들!

이와 같이 Shallow copy, Deep Copy, 임시 객체, r-value참조 등을 자유자재로 사용할 수 있다면, 메모리를 최소로 사용하며 효율적인 프로그래밍 작성이 가능할 것이다!

반응형

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ 5단원 복습! (2편)  (0) 2020.03.22
C++ 5단원 복습! (1편)  (0) 2020.03.22
C++ 4단원 복습! (1편)  (0) 2020.03.14
C++ 3단원 복습! (2편)  (0) 2020.03.12
C++ 3단원 복습! (1편)  (0) 2020.03.12

댓글