CPU는 덧셈이나 뺄셈과 같은 연산의 주체다 이건 누구나 다 아는 사실 하지만 좀 더 깊게 들어가면 ALU라는 녀석이 실제 연산을 담당한다. ALU 연산은 크게 두가지로 나뉜다 하나는 덧셈이나 뺄셈 같은 산술연산, 하나는 AND와 OR 같은 논리 연산이다.
Control Unit
프로그램을 컴파일하면 실행파일이 되고 이 실행파일에는 CPU에게 일을 시키는 명령어가 저장되어 있다.
이 명령어가 CPU 내부의 ALU로 전송되었다고 가정하자. 명령어는 다 0과 1로 구성되어있다(당연하다 컴퓨터는 0과 1밖에 모르니) 32비트 명령어라면 "00001111 00001111 00001111 00001111"와 같이 구성되어 있을 것이다.
과연 ALU가 0과 1로 구성되어있는 명령어를 이해할 수 있나? 못한다 ALU는 연산만 하니깐 이명령어를 해석해주는 놈이 바로 컨트롤 유닛이다.
레지스터(Register)
명령어가 CPU로 들어왔다고 가정하자. 덧셈 명령어 그리고 덧셈에 필요한 피연산자. 명령어는 컨트롤 유닛으로 피연산자는 ALU로 보내면된다. 하지만 ALU나 컨트롤 유닛이 지금 다른 명령어를 해석하고 있다면?? CPU 내부에 데이터를 저장해두고 CPU가 필요할 때 직접 가져다 쓰면 좋을 것 같다. 그렇다 CPU 내부의 조그마한 메모리 공간을 레지스터라고 한다.(연산할때 일일이 메모리에서 가져다가 연산하면 속도가 개판된다)
버스 인터페이스(Bus Interface)
명령어와 데이터가 CPU로 어떻게 들어 왔을까? 바로 버스 인터페이스 때문이다. 서로 데이터를 주고 받기 위해서 어떤 매개체가 필요하다 그것이 바로 버스 인터페이스다.
이동 생성자의 rhs는 무엇일까? 바로 temp이다. 그러니깐 return temp를 할 때 temp instance를 기반으로 이동 생성자가 불렸다는 뜻. 당연하다 복사 생성자도 temp instance를 기반으로 했으니.
이동 생성자에서는 new로 메모리 할당받아서 Deep Copy이런거 안한다.
그냥 rhs.name(이때 rhs는 temp 임시 객체 r-value 참조)의 주소 값(포인터니깐)을 name에 할당 그러니깐 주소값만 name에 넣어주고 끝이다. 왜냐하면 조만간 사라질 임시 객체니깐! 그리고 그 값을 고스란히 return 하고 아름답게 전사한다. 이때 return 한 임시 객체 값은 + c 의 연산과정에서 또 쓰이게 된다!
이로써 Deep Copy(복사생성)에서 메모리 할당받고 어쩌고 저쩌고를 포인터 하나로 아주 성능 좋게 바꿨다.
깊은 복사는 실제로 두 개의 값(인스턴스가) 생성 되는 것이고, 얕은 복사는 원본은 하난데 포인터가 두개인 것이다.
얕은복사로 인한 에러 발생
#include <iostream>
using namespace std;
class CMYData
{
public:
CMYData(int nParam)
{
m_pnData = new int;
*m_pnData = nParam;
};
~CMYData()
{
delete m_pnData;
};
int GetData(void) { return *m_pnData; };
private:
int *m_pnData = nullptr;
};
int main(void)
{
CMYData a(10);
CMYData b(a);
cout << a.GetData() << endl;
cout << b.GetData() << endl;
return 0;
}
위의 코드를 실행하면 인스턴스 b의소멸자를 Call 할 때 에러가 발생한다.
왜냐?? 진짜 중요한 개념인데 b의 Default 복사 생성자는 포인터를 제대로 처리하지 못하기 때문이다.
좀 더 자세히 말하면 인스턴스 b도 생성 될 때 새롭게 동적 할당 받아서 거기다가 값을 집어 넣야아하는데 Defalut 복사 생성자는 인스턴스 a의 m_pnData 값 즉 a에서 동적 할당 한 값을 그대로 b의 m_pnData에 집어 넣었기 때문이다. 그러니깐 인스턴스 a가 소멸될때 delete를 했는데 b가 소멸될 때 없는 메모리를 delete 하려 하니 에러가 발생하는것!
다 집어치우고 main함수에서 TestFunc(a)를 호출하면 복사 생성자가 호출된다 왜냐하면 TestFunc의 매개변수는 CMYData param이기 때문이다. 그러니깐 param은 CMYData의 새로운 인스턴스다. 인스턴스를 하나 더 만들었다.
골때린다. 그리고 그 새로운 인스턴스에 20값을 집어 넣어놓고는 인스턴스가 삭제됐다(스택 프레임 종료). 그러니 a는 여전히 10일 수 밖에.. 아무것도 못하고 성능 낭비 메모리 낭비만 하고 사라지는 거지같은 함수 TestFunc다. 이것을 아주 우아하게 바꾸려면 앞에 & 참조만 붙여주면 된다.
바로 void TestFunc(CMYData ¶m)이렇게! 이렇게 해야 call by reference로 주소만 넘긴다! 객체를 생성하지도 않고 성능도 아주 좋다.
- CMYData 클래스 안의 PrintData 메소드는 this를 통해서 m_Data를 접근하고 있다. a.PrintData()를 Call 할 때 this는 &a와 같은 값을 가지고, b.PrintData()를 Call 할 때 this는 &b와 같은 값을 가진다. a.PrintData()는 사실 a.PrintData(&a)와 같다. 파라미터로 &a를 안넘겨도 되는 이유는 C++가 알아서 처리 해주기 때문이다(히든 파라미터라고 부름)