전체코드
#include <iostream>
#include <cstring>
using namespace std;

class MyClass 
{
public:
	MyClass(const char _name[])
	{
		this->name = new char[strlen(_name) + 1];
		strcpy_s(this->name, strlen(_name) + 1, _name);
		cout << "기본 생성자 MyClass()" << this->name << endl;
	}

	MyClass(const MyClass& rhs)
	{
		this->name = new char[strlen(rhs.name) + 1];
		strcpy_s(this->name, strlen(rhs.name) +1, rhs.name);
		cout << rhs.name << "@@@@@@@@@@@복사 생성자 MyClass(const MyClass &rhs)@@@@@@@@@@" << endl;
	}

	//MyClass(MyClass&& rhs) : name(rhs.name)
	//{
	//	cout << rhs.name << "########## 이동 생성자(const MyClass&& rhs)##########" << endl;
	//  rhs.name = nullptr;
	//}

	operator string() { return this->name; }

	MyClass operator+(const MyClass& rhs)
	{
		cout << "operator+" << endl;
		MyClass temp(rhs.name);
		int thisNameLen = strlen(this->name);
		int rhsNameLen = strlen(rhs.name);
		strcat_s(temp.name, (thisNameLen + rhsNameLen + 1), this->name);
		return temp;
	}

	MyClass& operator=(const MyClass& rhs)
	{
		cout << "operator=" << endl;
		return *this;
	}

	void setName(const char _name[])
	{
		delete this->name;
		this->name = new char[strlen(_name) + 1];
		strcpy_s(this->name, strlen(_name) + 1, _name);
	}

	void printName()
	{
		cout << this->name << endl;
	}

private:
	char *name;
};


int main() 
{
	cout << "##########begin#########" << endl;
	MyClass a("aa"), b("bb"), c("cc");
	MyClass d = a + b + c;
	a.printName();
	cout << string(a) << endl;

	b.printName();
	cout << string(b) << endl;

	c.printName();
	cout << string(c) << endl;

	d.printName();
	cout << string(d) << endl;



	cout << "##########end#########" << endl;
	return 0;
}


// 출력값
##########begin#########
기본 생성자 MyClass()aa
기본 생성자 MyClass()bb
기본 생성자 MyClass()cc
operator+
기본 생성자 MyClass()bb
bbaa@@@@@@@@@@@복사 생성자 MyClass(const MyClass &rhs)@@@@@@@@@@
operator+
기본 생성자 MyClass()cc
ccbbaa@@@@@@@@@@@복사 생성자 MyClass(const MyClass &rhs)@@@@@@@@@@
aa
aa
bb
bb
cc
cc
ccbbaa
ccbbaa
##########end#########

MyClass라는 클래스를 만들고 인스턴스 a,b,c를 각각 정의했다.

이때는 당연히 기본생성자가 불린다.

파라미터 값으로 문자열을 받으니 _name[]으로 정의했다.

그런다음 name 포인터에 문자열을 담는 아주 간단한 기본 생성자이다.

 

문제는 MyClass d = a +b + c; 여기에 있다.

골때리게도 이 한줄이 이렇게 많은 생성자를 호출한다...

생성자를 호출한다는건 인스턴스가 생성된다는 뜻이고 여기에 따른 메모리, CPU Loss가 엄청나다.

 

여기서 주목해야할 부분은 바로 복사 생성자와 operator+ 더하기 연산자다. 

복사 생성자에서는 메모리 할당 문제 때문에 Deep Copy를 수행한다. (이전 글 참조)

2020/02/01 - [C++] - C++ 복사 생성자(참조형 파라미터)

 

C++ 복사 생성자(참조형 파라미터)

복사 생성자는 객체의 복사본을 생성할 때 호출되는 생성자이다. CMYData a CmyData b(a) 이처럼 원본 a의 복사본을 만들때 복사 생성자가 불린다. 문법은 클래스이름(const 클래스이름 &rhs) 처럼 사용한다. #inc..

leemoney93.tistory.com

operator + 를 살펴보자.

 

operator+
	MyClass operator+(const MyClass& rhs)
	{
		cout << "operator+" << endl;
		MyClass temp(rhs.name);
		int thisNameLen = strlen(this->name);
		int rhsNameLen = strlen(rhs.name);
		strcat_s(temp.name, (thisNameLen + rhsNameLen + 1), this->name);
		return temp;
	}
    
    a + b === a.operator+(b)

operator+에서는 할말이 아주 많은데 천천히 하나씩 해보면

1. operator+는 a+b를 할때 불리는 연산자이다.

2. a+b는 내부적으로 a.operator+(b)랑 같다

3. 그러므로 위의 코드 operator+의 rhs 값은 오른쪽 즉 b의 인스턴스의 참조이다.

4. operator+ 내부에서 temp 인스턴스를 생성하는데 b의 name값을 가지고 생성한다.

 ※ 기본 생성자 MyClass()bb 출력

5. a의 name값과 b의 name값을 합쳐서(strcat_s) MyClass instance temp를 return 한다.

 - (중요)이때 복사 생성자가 출력되는데 그 이유는 return temp를 할때 임시 객체를 생성하고 return 하기 때문이다.

MyClass(const MyClass& rhs)
{
  this->name = new char[strlen(rhs.name) + 1];
  strcpy_s(this->name, strlen(rhs.name) +1, rhs.name);
  cout << rhs.name << "@@@@@@@@@@@복사 생성자 MyClass(const MyClass &rhs)@@@@@@@@@@" << endl;
}
// 출력값
bbaa@@@@@@@@@@@복사 생성자 MyClass(const MyClass &rhs)@@@@@@@@@@

6. 위의 코드는 복사 생성자가 불릴땐데 rhs.name은 bbaa이다.

그러니깐 위의 temp instance를 기반으로 복사 생성이 이루어졌단뜻!

왜냐?? a + b를 하면 + c를 또 해야되는데(d = a + b + c) a + b의 결과 값을 가지는 임시 객체를 만들어야 하기 때문에 복사 생성이 필요하단 뜻이다. 근데 도대체 이동 생성이랑 무슨 관계인데?? 

위의 복사 생성은 Deep Copy를 하고있다. 아니 조만간 사라질놈이 왜 Deep 카피를 하냐 성능 떨어지게 그냥 포인터로 간단하게 끝내서 성능향상을 하면 되잖아! 이 논리로 나온게 바로 이동 생성이다.

MyClass(MyClass&& rhs) : name(rhs.name)
{
    cout << rhs.name << "########## 이동 생성자(const MyClass&& rhs)##########" << endl;
    rhs.name = nullptr;
}
    
// 출력값
bbaa########## 이동 생성자(const MyClass&& rhs)##########

전체코드의 주석을 풀자.

그럼 복사 생성자 대신 이동 생성자가 불린다.

이동 생성자의 rhs는 무엇일까? 바로 temp이다. 그러니깐 return temp를 할 때 temp instance를 기반으로 이동 생성자가 불렸다는 뜻. 당연하다 복사 생성자도 temp instance를 기반으로 했으니.

 

이동 생성자에서는 new로 메모리 할당받아서 Deep Copy이런거 안한다.

그냥 rhs.name(이때 rhs는 temp 임시 객체 r-value 참조)의 주소 값(포인터니깐)을 name에 할당 그러니깐 주소값만 name에 넣어주고 끝이다. 왜냐하면 조만간 사라질 임시 객체니깐! 그리고 그 값을 고스란히 return 하고 아름답게 전사한다. 이때 return 한 임시 객체 값은 + c 의 연산과정에서 또 쓰이게 된다!

 

이로써 Deep Copy(복사생성)에서 메모리 할당받고 어쩌고 저쩌고를 포인터 하나로 아주 성능 좋게 바꿨다.

 

 

'C++' 카테고리의 다른 글

C++ 멤버함수의 메모리 위치  (0) 2020.04.09
C++ 객체 생성  (0) 2020.04.02
C++ 대입 연산자  (0) 2020.02.02
C++ 깊은복사 얕은복사  (0) 2020.02.02
C++ 복사 생성자(참조형 파라미터)  (0) 2020.02.01

+ Recent posts