1. 암시적 멤버 메서드 6가지

  • 생성자, 소멸자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자
  • '암시적'이라는 키워드에서 알 수 있듯이 이 6가지의 함수들은 클래스가 정의되면 적혀있지 않아도 적혀있는 것이다.
class A
{
//생성자, 소멸자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자가 안 보이지만 있다.
};
class A
{
public:
	A() { cout << "생성자" << endl; }
	~A() { cout << "소멸자" << endl; }
	A(const A& Rhs) { cout << "복사 생성자" << endl; }
	A& operator=(const A& Rhs) { cout << "복사 대입 연산자" << endl; return *this; }
	A(A&& Rhs) noexcept { cout << "이동 생성자" << endl; }
	A& operator=(A&& Rhs) noexcept { cout << "이동 대입 연산자" << endl; return *this; }

public:
    int a = 10;
};

 

2. 이 함수들은 언제 불리는가 ?

int main()
{
	{
		A MyA; //생성자 불림
		A MyOtherA(MyA); //복사 생성자 불림
		MyOtherA = MyA; //복사 대입 연산자 불림
		MyOtherA = std::move(MyA); //이동 대입 연산자 불림
		A MyOtherB(std::move(MyOtherA)); //이동 생성자 불림
		//스코프를 벗어나면 만들어졌던 A들의 소멸자가 불림
	}

	return 0;
}
  • 한 가지 특이사항으론 A MyOtherA(MyA); 가 복사 생성자를 불렀는데 A MyOtherA = MyA; 도 복사 생성자를 부른다.
  • 언뜻 생각하기엔 복사 대입 연산자를 불러야 하는 것이 아닌가 ? 라고 생각할 수 있는데 생성과 동시에 초기화를 하는 것이기 때문에 복사 '생성자'가 불리는 것이다. 생성 이후 '=' 을 사용하게 된다면 그 때서야 복사 대입 연산자가 불리게 된다.
  • 위 논리는 이동 에도 마찬가지로 적용된다.

 

3. 복사 vs 이동

  • 이동이 복사보다 대체로 성능이 좋다.(무조건은 아니라고 한다. 클래스의 크기가 커질수록 복사가 부담되는 상황이 있을 수 있다는 것)
  • 이동이 무조건 좋다는게 아니다. 이동에 당한 대상은 정보를 잃게 된다. 성능향상을 꾀하며 이젠 필요없을 대상에 이동을 하면 된다.
  • 복사는 l value에 하고 이동은 r value에 할 수 있는 행동이라고 볼 수 있고 정확한 설명은 아닐 수 있지만 &&은 r value를 받겠다는 의미이고 std::move는 r value로 취급할 수 있게끔 하는 것이다.
  • 이동을 했다고 그 공간이 비워진다거나 하는 것은 아니다. 비워졌다고 취급하는 것이다. 이 또한 정확한 설명은 아니겠지만 내가 테스트해본 코드에서는 그렇다.
int main()
{
	{
		A MyA; //생성자 불림
		A MyOtherA(MyA); //복사 생성자 불림
		MyOtherA = MyA; //복사 대입 연산자 불림
		MyOtherA = std::move(MyA); //이동 대입 연산자 불림
		A MyOtherB(std::move(MyOtherA)); //이동 생성자 불림
		cout << MyOtherA.a << endl;
		//스코프를 벗어나면 만들어졌던 A들의 소멸자가 불림
	}

	return 0;
}
  • 위 코드에서 이동이 일어나서 MyOtherA가 텅 비었다면 a는 값이 찍히면 안되지만 10의 값을 찍어낼 수 있다.
  • 그렇기 때문에 이동 연산에서도 넘어온 값에 대해 적절한 조치를 취해 '이동되었음'을 명시해주는 것이 좋다.

 

4. l value, r value

  • left, right / 표현식 이후에도 사리지지 않는, 표현식 이후엔 사라지는
  • 계속 헷갈렸던 개념인데 https://blog.naver.com/luku756/221808884092 이 블로그 글이 도움이 되었다.
  • 위 글에서 감명깊었던 부분은 x++;은 l value인 실제 값은 올리고 올리기 전 기존 값을 r value로서 return한 것이라는 사실, 그렇기 때문에 &(x++);은 r value의 주소를 특정할 수 오류라는 사실
  • 또한 int&& a = 10; 이라는 변수는 10이라는 r value를 담아두는 l value라는 사실
  • 위 예제 클래스에서 이동 생성자, 이동 대입 연산자에 매개변수로 사용한 Rhs는 l value라는 것이다.

 

5. 얕은 / 깊은 복사

  • 클래스의 기본 메서드 6가지 중에 복사 생성자, 복사 대입 연산자는 기본적으로 수행하는 기능이 있다.
  • 멤버변수의 '값'을 '복사'하는 것이다.
  • 이 복사라는 것이 일반적인 변수에 수행하였을 경우엔 문제가 없으나 '포인터' 변수에 기본 복사 메서드를 수행한다면 문제가 생길 수 있다.
  • '포인터'란 주소를 갖고 있는 변수이고, 다른 인스턴스지만 같은 주소를 가진 변수가 있다면 같은 변수를 같이 쓰는 꼴이 된다.
  • 그렇기 때문에 '깊은 복사'를 해주어야 한다.
  • 단순히 포인터의 주소를 복사해오는 것이 아닌 포인터의 원본에 접근하여 원본이 어떤 값이었는지를 알아와 복사해줘야 한다는 뜻이다.
  • 위 얘기까지만 듣고 클래스의 기본 복사 메서드가 '얕은 복사'라고 착각할 수도 있지만 그건 아니다. 멤버변수 중에 포인터가 있다면 기본 복사 메서드가 수행될 때 '얕은 복사'가 되었다고 할 수 있다.
#include <iostream>

class A
{
public:
	A()
	{
		pa = new int;
		*pa = 10;
	}

	//~A()
	//{
	//	delete pa;
	//}

public:
	int* pa;
};

int main()
{
	A MyA;
	A MyB(MyA);

	std::cout << MyA.pa << std::endl;
	std::cout << MyB.pa << std::endl;

	*(MyB.pa) = 20;

	std::cout << *(MyA.pa) << std::endl;
	std::cout << *(MyB.pa) << std::endl;

	return 0;
}
  • 위 결과는 MyA와 MyB가 같은 포인터 주소를 가리켰고 MyB의 pa가 가진 원본을 바꿨더니 MyA와 MyB의 pa 값이 둘다 영향 받은 것을 볼 수 있다.
  • 또한 소멸자가 할당했던 메모리에 대해서 해제를 시도하는 코드인데 주석을 친 이유는 main이 끝나면서 MyA와 MyB가 사라지면서 소멸자가 호출될 때 같은 메모리 주소를 2번 해제하려고 했기 때문에 크래쉬가 났기 때문이다.
  • 혹시나 해서 적지만 main이 끝나고 프로그램이 종료되면 위 코드가 '메모리 누수'를 일으키지는 않는다. 운영체제가 알아서 정리해준다.
  • 위 상황에 올바른 코드 작성을 보자.
#include <iostream>

class A
{
public:
	A()
	{
		pa = new int;
		*pa = 10;
	}

	A(const A& Rhs)
	{
		pa = new int;
		*pa = *Rhs.pa;
	}

	~A()
	{
		std::cout << "소멸자 정상 호출" << std::endl;
		delete pa;
	}

public:
	int* pa = nullptr;
};

int main()
{
	A MyA;
	A MyB(MyA);

	std::cout << MyA.pa << std::endl;
	std::cout << MyB.pa << std::endl;

	*(MyB.pa) = 20;

	std::cout << *(MyA.pa) << std::endl;
	std::cout << *(MyB.pa) << std::endl;

	return 0;
}

'C++ > 키워드 정리' 카테고리의 다른 글

키워드 정리 [14] - RTTI  (1) 2023.12.01
키워드 정리 [13] - 가상 함수  (0) 2023.12.01
키워드 정리 [11] - OOP  (1) 2023.12.01
키워드 정리 [10] - Template  (0) 2023.12.01
키워드 정리 [9] - 포인터  (1) 2023.12.01

+ Recent posts