[열혈 C++] Chap11: 연산자의 오버로딩2 - 1) 대입 연산자 오버로딩
1. 대입연산자 오버로딩
- 디폴트 대입 연산자
- 정의하지 않으면 디폴트 대입 연산자가 삽입된다
- 디폴트 대입연산자는 멤버 대 멤버의 얕은 복사를 진행한다
- 연산자 내에서 동적할당을 한다면, 그리고 깊은 복사가 필요하다면 직접 정의해야 한다.
- 위와 같이 복사생성자와 매우 유사하지만, 호출시점이 다르다
//복사생성자 호출 시점 int main(void){ Point pos1(5,7); Point pos2 = pos1; } //대입연산자 호출 시점 int main(void){ Point pos1(5,7); Point pos2(10,6); pos2 = pos1; }
- 객체 두개가 모두 생성 및 초기화 된 상태에서 대입할 때에는 대입 연산자가 호출된다.
- 앞서 배운 연산자 오버로딩에 따라
pos2=pos1;
은pos2.operator=(pos1);
로 해석될 수 있다.
#include <iostream>
using namespace std;
class First
{
private:
int num1, num2;
public:
First(int n1=0, int n2=0) : num1(n1), num2(n2)
{ }
void ShowData() { cout<<num1<<", "<<num2<<endl; }
};
class Second
{
private:
int num3, num4;
public:
Second(int n3=0, int n4=0) : num3(n3), num4(n4)
{ }
void ShowData() { cout<<num3<<", "<<num4<<endl; }
Second& operator=(const Second& ref)
{
cout<<"Second& operator=()"<<endl;
num3=ref.num3;
num4=ref.num4;
return *this;
}
};
int main(void)
{
First fsrc(111, 222);
First fcpy;
Second ssrc(333, 444);
Second scpy;
fcpy=fsrc;
scpy=ssrc;
fcpy.ShowData();
scpy.ShowData();
First fob1, fob2;
Second sob1, sob2;
fob1=fob2=fsrc;
sob1=sob2=ssrc;
fob1.ShowData();
fob2.ShowData();
sob1.ShowData();
sob2.ShowData();
return 0;
}
//실행결과
/*Second& operator=()
111,222
333,444
Second& operator=()
Second& operator=()
111,222
111,222
333,444
333,444
*/
- 디폴트 대입연산자는 Second에 정의된 연산자 오버로딩된 함수와 같다.
✏️디폴트 대입 연산자 문제점
- 복사생성자 문제점과 유사하다
-
동적할당 된 변수가 다른 메모리 공간에 새로 복사되는 것이 아니라 같은 메모리 공간에 있는 변수의 주소가 복사되는 것이기 때문에, 중복소멸해버리는 문제가 생긴다.
-
원래 멤버변수로 가지고 있던 문자열 변수의 주소 값을 잃게 된다. 따라서 메모리 릭이 발생한다.
-
- 따라서 대입연산자는 기존의 문자열 메모리를 해제하고, 깊은 복사를 진행해야 한다.
✏️상속에서 대입 연산자 호출
- 대입연산자는 생성자가 아니기 때문에 유도클래스의 대입 연산자에 아무런 명시를 하지 않으면, 기초클래스의 대입연산자가 호출되지 않는다. 생성자는 유도클래스의 객체가 만들어질 때, 자동으로 호출되지만 대입연산자는 아니라는 얘기
#include <iostream>
using namespace std;
class First
{
private:
int num1, num2;
public:
First(int n1=0, int n2=0) : num1(n1), num2(n2)
{ }
void ShowData() { cout<<num1<<", "<<num2<<endl; }
First& operator=(const First&ref)
{
cout<<"First& operator=()"<<endl;
num1=ref.num1;
num2=ref.num2;
return *this;
}
};
class Second : public First
{
private:
int num3, num4;
public:
Second(int n1, int n2, int n3, int n4)
: First(n1, n2), num3(n3), num4(n4)
{ }
void ShowData()
{
First::ShowData();
cout<<num3<<", "<<num4<<endl;
}
Second& operator=(const Second &ref)
{
cout<<"Second& operator=()"<<endl;
// First::operator=(ref);
num3=ref.num3;
num4=ref.num4;
return *this;
}
};
int main(void)
{
Second ssrc(111, 222, 333, 444);
Second scpy(0, 0, 0, 0);
scpy=ssrc;
scpy.ShowData();
return 0;
}
/*실행결과
Second& operator=()
0,0
333,444
*/
- 위와 같이 First를 상속받은 Second의 대입연산자 함수에 기초클래스의 대입연산자 함수를 직접 호출하지 않으면 기초클래스의 멤버변수는 복사되지 않는다.
✏️이니셜라이저 성능향상
-
이니셜라이저를 사용하면 성능이 향상되는 이유
- 이니셜라이저는 선언과 동시에 초기화되기 때문에 복사 생성자만 호출된다.
- 생성자 몸체에 대입연산자로 초기화 시키면, 선언과 초기화가 각각 별도로 일어나기 때문에, 생성자도 호출되고 대입연산자도 호출된다.