TIL 2024/9/4

Sung Joo Lee·2024년 9월 4일

첨자 연산자 오버로딩

  • [ ] 연산자 ( 배열에 사용)
  • 멤버 함수로 오버로딩 필요
    • ,( ), → , = 와 같은 몇몇 연산자는 멤버 함수로만 오버로딩 가능
  • 경계 검사 등 기능 확장을 위해 용이하게 사용됨
#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;
		//}
		return ptr[index];
	}
	
	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 };

	std::cout << a1.GetValue(1) << std::endl;
	std::cout << a1.GetValue(-1) << std::endl; //쓰레기 값
	std::cout << a1.GetValue(15) << std::endl; //쓰레기 값

}

경계 검사 : 내가 사용하겠다고 한 범위 (위의 코드에서는 0~9 index 까지)에서만 안전하게 사용가능하도록 검사하는 것. 위의 -1,15는 range 범위 밖의 값이기 때문에 접근이 불가능 하도록 해주는 것이 좋다.

경계 검사 적용 코드

#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;
	}

	
	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 operator[](int index)
	{
		if (index < 0 || index > size) //경계 검사
		{
			std::cout << "Out of Range!" << std::endl;
			exit(1);// 1을 출력해주고 종료 됨 (문제를 알려주는 것)
		}
		else
			return ptr[index];
	}
};

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

	std::cout << a1[1];//a1.operator[](1)로 변환
	std::cout << a1[-1];// exited with code 1

}

operator[ ]를 멤버 함수로 선언하여 경계 검사를 진행해 주고 내가 할당한 range 밖에 접근할 때 exit(1)을 사용하여 1을 호출해주고 오류를 알려준다.

참조자 반환 첨자 연산자 오버로딩

  • 첨자 연산자 오버로딩의 구현
Point& operator[](int idx)
{
		if( idx < 0 || idx >= arr_len)
		{
				cout << "Array out of bound!" << endl;
				exit(1);
		
		}
		return arr[idx];
}
  • 참조형으로 반환해야만 arr[0] = Point{ 10, 20} 과 같이 배열 내부 데이터에 접근 가능
    • 참조형으로 반환하지 않으면 복사된 값이 반환된다는 것을 상기
💡

위에서 배운 코드로 예를 들어보면

	int& operator[](int index)
	{
		if (index < 0 || index > size) //경계 검사
		{
			std::cout << "Out of Range!" << std::endl;
			exit(1);// 1을 출력해주고 종료 됨 (문제를 알려주는 것)
		}
		else
			return ptr[index];
	}

위와 같이 참조자로 반환을 해야한다 그렇지 않다면 ptr[index]의 값을 ‘복사'하여 반환해 줄 것이고 5 = 10; 과 같이 상수에 상수를 대입하려 할 것이다. 이때 참조자로 반환을 해야 a[0] = 10;과 같이 배열에 값을 할당 가능하다.

첨자 연산자 const

#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;
	}

	
	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& operator[](int index)//값의 변환 가능 ( 참조형으로 반환하니까!)
	{
		if (index < 0 || index > size) //경계 검사
		{
			std::cout << "Out of Range!" << std::endl;
			exit(1);// 1을 출력해주고 종료 됨 (문제를 알려주는 것)
		}
		else
			return ptr[index];
	}

	int operator[](int index) const//값의 변환 불가능
	{
		if (index < 0 || index > size) //경계 검사
		{
			std::cout << "Out of Range!" << std::endl;
			exit(1);// 1을 출력해주고 종료 됨 (문제를 알려주는 것)
		}
		else
			return ptr[index];
	}
};

int main()
{
	const Array a1{ 5,10 };//값의 변형 불가능
	std::cout << a1[0]; //const 함수 호출 함
	
	Array a2{ 3,5 };
	a2[0] = 10;//값의 변형

}

main에 const 객체와 그냥 객체가 있을 때 첨자 연산자 오버로딩은 위와 같이 따로따로 구현해 줘야한다.

  • 사용 목적이 전혀 다르기 때문에 반환형이 다르고 const 객체에서는 컴파일러가 const 함수를 찾기 때문에 반드시 따로 구현 해줘야한다.

  • 그럼 아래와 같이 하면 안돼?

	int& operator[](int index) const
	{
		if (index < 0 || index > size) //경계 검사
		{
			std::cout << "Out of Range!" << std::endl;
			exit(1);
		}
		else
			return ptr[index];
	}

컴파일러 오류가 발생해서 안된다. (사실 안돼야 하는데 컴파일러에 따라 가능한 경우도 있는 것 같다… like me), int&이 반환형이기 때문에 const와 모순이 된다. 이는 컴파일러가 error처리를 한다.

객체의 복사와 관련해서 고려해야 할 점

  • 대입 연산자 오버로딩에서 배열에 대한 복사 생성 허용 여부의 결정이 필요
  • 배열에 대한 복사 생성과 대입을 차단하고 싶을 경우
    • 보통 안전성을 위해서 잘 허용해 주지

두 가지 방법이 존재

  1. private안에 선언하기
#include <iostream>

class Array {
private:
	int* ptr;
	int size;

	Array& operator=(const Array& rhs)//대입 연산자
	{

	}

	Array(const Array& other)//복사 생성자
	{

	}

public:

///중략~~

};

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

	Array a2{ 3,5 };

	a2 = a1;//a2.operator=(a1) 불가능
	Array a3 = a1;//복사 생성자 호출 됨
}
  1. = delete 사용
#include <iostream>

class Array {
private:
	int* ptr;
	int size;

public:
	Array& operator=(const Array& rhs) = delete;
	Array(const Array& other) = delete;

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

//중략~
};

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

	Array a2{ 3,5 };

	a2 = a1;//a2.operator=(a1) 불가능
	Array a3 = a1;//복사 생성자 호출 됨
}

해당 함수를 구현하지 못하도록 delete로 명시해 주는 것! 자동 생성도 불 가능함

profile
개발로그

0개의 댓글