2 minute read

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에 정의된 연산자 오버로딩된 함수와 같다.

✏️디폴트 대입 연산자 문제점

  • 복사생성자 문제점과 유사하다
    1. 동적할당 된 변수가 다른 메모리 공간에 새로 복사되는 것이 아니라 같은 메모리 공간에 있는 변수의 주소가 복사되는 것이기 때문에, 중복소멸해버리는 문제가 생긴다.

    2. 원래 멤버변수로 가지고 있던 문자열 변수의 주소 값을 잃게 된다. 따라서 메모리 릭이 발생한다.

  • 따라서 대입연산자는 기존의 문자열 메모리를 해제하고, 깊은 복사를 진행해야 한다.

✏️상속에서 대입 연산자 호출

  • 대입연산자는 생성자가 아니기 때문에 유도클래스의 대입 연산자에 아무런 명시를 하지 않으면, 기초클래스의 대입연산자가 호출되지 않는다. 생성자는 유도클래스의 객체가 만들어질 때, 자동으로 호출되지만 대입연산자는 아니라는 얘기
#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의 대입연산자 함수에 기초클래스의 대입연산자 함수를 직접 호출하지 않으면 기초클래스의 멤버변수는 복사되지 않는다.

✏️이니셜라이저 성능향상

  • 이니셜라이저를 사용하면 성능이 향상되는 이유

  • 이니셜라이저는 선언과 동시에 초기화되기 때문에 복사 생성자만 호출된다.
  • 생성자 몸체에 대입연산자로 초기화 시키면, 선언과 초기화가 각각 별도로 일어나기 때문에, 생성자도 호출되고 대입연산자도 호출된다.

Tags: ,

Categories:

Updated: