대입 연산자(=) 오버로딩


  • CPP는 대입 연산자를 자동 생성해줌
  • 자동 생성된 대입 연산자는 얕은 복사를 수행
    • 우리가 오버로딩하지 않았어도 객체에 대해서 사용이 가능하다.
Point p1 {10,20};
Point p2 {30,40};

Point p3 = p1; **//대입 연산이 아닌 복사 생성이다.**
p3.ShowPosition();

Point p4;
p4 = p1; //대입 연산
p4.ShowPosition();

객체 1를 생성하며 ‘ = 객체 2 ‘ 가 올 때는 객체 2를 복사하여 객체 1을 만들겠다는 의미, 즉 복사 생성자

이미 만들어진 객체에 ‘ = ’ 가 올 때 대입 연산자가 호출 됨

대입 연산자 오버로딩, 선언

  • 기본 패턴
    Type& operator=(const Type& rhs);
  • 참조형으로 반환하여 추가적인 복사 연산을 하지 않도록 함

    • 참조형으로 반환하지 않을 때 어떻게 될까?
      • 연쇄적으로 사용하지 못함
        • ex) a4 = a3 = a2
  • 사용 예

    Point& operator=(const Point& rhs)
    
    p4 = p1;//대입 연산을 할 경우 아래의 함수가 호출 됨
    p4.operator=(p1);

대입 연산자 오버로딩, 구현

Point& operator=(const Point& rhs)**//없으면 컴파일러가 자동 구현해줌**
{
		//예외처리하는 이유?, 멤버 변수가 포인터라면? -> 얕은 복사가 되기 때문에
		if ( this == &rhs)
					return *this;
					
		xpos = rhs.xpos;
		ypos = rhs.ypos;
		
		return *this;

}

여기서 if로 예외처리 하는 이유는 표면적으로 봤을 때 같은 객체를 대입하는 것을 막기위해, 나머지 하나는 얕은복사 문제가 생길 수 있기 때문에 예외처리를 한 것이다.

대입 연산자 오버로딩, 깊은 복사

  • CPP은 대입 연산자도 자동 생성해줌
  • 자동 생성된 대입 연산자는 얕은 복사를 수행
  • 포인터 타입의 멤버 변수가 존재하면, 깊은 복사를 통한 대입 연산자 직접 정의 필요!

복사 생성자 복습

  • 복사 생성자는 자동 생성됨
  • 자동 생성된 복사 생성자는 얕은 복사 수행
  • 포인터 타입의 멤버 변수가 존재하는 경우, 깊은 복사의 직접 정의가 필요

예제 코드

#include <iostream>

class Array {
private:
	int* ptr;
	int size;
public:
	Array(int val, int size)
		:size{ size }
	{
		ptr = new int[size];//배열 동적생성
		for (int i = 0; i < size; i++)
		{
			ptr[i] = val + i;
		}
	}

	int GetSize() const
	{
		return size;
	}

	int GetValue(int index) const
	{
		if (index < size && index >= 0)//index 범위 안에 있다면
			return ptr[index];
		else
		{
			std::cout << "Out of Range!!" << std::endl;
		}
	}

	~Array()
	{
		delete[] ptr;
	}
};

int main()
{
	Array a1{ 5,10 };

	Array a2{ 3,5 };

	a2 = a1; // 대입 연산자 자동 생성, 의도대로 동작하는지 모름
			 // ERROR 발생함
	
	std::cout << a2.GetValue(0) << std::endl;
}

멤버 변수에 포인터가 있는 것을 생각해보면 a2의 멤버 변수의 값에 a1의 변수의 값을 복사해서 할당하면 a1.ptr와 a2.ptr이 같은 공간을 가리키게 된다. 즉, 이는 얕은 복사 문제발생하여 ‘이중 해제’ 문제가 발생하게 된다는 의미이다.

해결

  • 이를 해결하기 위해서 ‘대입 연산자’를 구현해야 한다.
#include <iostream>

class Array {
private:
	int* ptr;
	int size;
public:
	Array(int val, int size)
		:size{ size }
	{
		ptr = new int[size];//배열 동적생성
		for (int i = 0; i < size; i++)
		{
			ptr[i] = val + i;
		}
	}

	int GetSize() const
	{
		return size;
	}

	int GetValue(int index) const
	{
		if (index < size && index >= 0)//index 범위 안에 있다면
			return ptr[index];
		else
		{
			std::cout << "Out of Range!!" << std::endl;
		}
	}
	
	Array& operator=(const Array& rhs)//대입 연산자
	{
		if (this == &rhs)//동일 객체 대입 방지
			return *this;//객체 반환

		
		delete[] ptr;//메모리 누수 방지

		size = rhs.size;
		ptr = new int[size];

		for (int i = 0; i < size; i++)
		{
			ptr[i] = rhs.ptr[i];
		}
	}

	~Array()
	{
		delete[] ptr;
	}
};

int main()
{
	Array a1{ 5,10 };

	Array a2{ 3,5 };

	a2 = a1; 
	
	std::cout << a2.GetValue(0) << std::endl;
}
  • 대입 연산자에서 얕은 복사를 피하기 위해서 새로운 공간을 할당해서 해당 공간안에 직접 a1의 값들을 복사 해줬다.

  • 이때 a2가 이미 가리키고 있던 객체를 해제시켜 메모리 누수가 발생하지 않도록 한다.

  • 대입 연산자는 “return *this;”를 해야한다.

return *this

이해가 가지 않아서 공부해봄.

Point& operator=(const Point& rhs)**//없으면 컴파일러가 자동 구현해줌**
{
		if ( this == &rhs)
					return *this;
					
		xpos = rhs.xpos;
		ypos = rhs.ypos;
		
		return *this;

}

내가 생각한건 *this는 객체 그 자체를 말하는 것인데 어떻게 함수의 반환형이 Point로 될 수 있지?’ 였다.

(https://hwan-shell.tistory.com/38 해당 블로그를 참조해서 공부하였다)

  • 즉, *this는 포인터의 참조 개념이라고 생각하면 된다.
profile
개발로그

0개의 댓글