※ 연산자 오버로딩 기본 개념 참고
대입 연산자
- 정의하지 않으면 디폴트 대입 연산자가 삽입된다.
- 복사 생성자와 마찬가지로 멤버 대 멤버의 복사가 일어난다.
- 디폴트 대입 연산자는 얕은 복사이기 때문에 깊은 복사가 필요하다면 직접 정의해야한다.
#include <iostream>
using namespace std;
class SomeClass
{
private:
int n1;
public:
SomeClass(int n1 = 0) : n1(n1) { }
void ShowData()
{
cout << n1 << endl;
}
SomeClass& operator=(const SomeClass& ref)
{
cout << "SomeClass operator=()" << endl;
this->n1 = ref.n1;
return *this;
}
};
void main()
{
SomeClass sc1(10);
SomeClass sc2;
sc2 = sc1;
sc2.ShowData();
//결과
//SomeClass operator=()
//10
}
- sc2 선언시 바로 대입 연산자를 사용하면 대입 연산자가 호출 되는것이 아닌 복사 생성자가 호출이 된다.
디폴트 대입 연산자의 문제점
- 복사 생성자에서 생기는 문제와 유사하다. (얕은 복사의 문제)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;
class Name
{
private:
char* name;
public:
Name(const char* name)
{
int len = strlen(name) + 1;
this->name = new char[len];
strcpy(this->name, name);
}
void ShowData()
{
cout << name << endl;
}
Name& operator=(const Name& ref)
{
delete[] name;
int len = strlen(ref.name) + 1;
this->name = new char[len];
strcpy(this->name, ref.name);
return *this;
}
~Name()
{
delete[] name;
}
};
void main()
{
Name n1("Lee");
Name n2("Yoon");
n2 = n1;
n2.ShowData();
//결과 : Lee
}
- 대입 연산자를 통한 얕은 복사에서 일어나는 메모리 누수를 막기위해 name 멤버 변수에 할당된 기존 데이터를 할당 해제하고 새롭게 할당하여 깊은 복사를 진행한다.
상속 구조에서의 대입 연산자
- 자식 클래스에서 대입 연산자를 오버로딩 하는 경우, 부모 클래스의 대입 연산자를 명시해 주지 않으면 부모 클래스의 대입 연산자는 호출이 되지 않는다.
- 따라서, 디폴트 대입 연산자의 경우 부모 클래스의 대입 연산자까지 호출되는 것을 확인 할 수 있다.
#include <iostream>
using namespace std;
class Parent
{
private:
int n1, n2;
public:
Parent(int n1 = 0, int n2 = 0) : n1(n1), n2(n2) { }
void ShowData()
{
cout << n1 << " / " << n2 << endl;
}
Parent& operator=(const Parent& ref)
{
cout << "Parent operator=()" << endl;
this->n1 = ref.n1;
this->n2 = ref.n2;
return *this;
}
};
class Child : public Parent
{
private:
int n1, n2;
public:
Child(int n1, int n2, int n3, int n4) : Parent(n1, n2), n1(n3), n2(n4) { }
void ShowData()
{
Parent::ShowData();
cout << n1 << " / " << n2 << endl;
}
Child& operator=(const Child& ref)
{
cout << "Child operator=()" << endl;
//부모 클래스 대입 연산자 호출
Parent::operator=(ref);
this->n1 = ref.n1;
this->n2 = ref.n2;
return *this;
}
};
void main()
{
Child c1(1, 2, 3, 4);
Child c2(0, 0, 0, 0);
c2 = c1;
c2.ShowData();
//결과
//Child operator=()
//Parent operator=()
//1 / 2
//3 / 4
}
- Child 클래스에서 대입 연산자 오버로딩에 Parent::operator(ref)를 주석처리하고 실행해보면 부모 클래스의 멤버는 대입 연산자가 작용하지 않은것을 확인할 수 있다.
인덱스 연산자
- C, C++의 배열은 '경계검사를 하지 않는다'는 단점을 가지고 있다.
- 즉 배열의 범위에 벗어난 인덱스 값을 넣어도 실행하는데 무리없이 진행이 되기 때문에, 안정성을 고려해서 배열 클래스를 만든다.
#include <iostream>
using namespace std;
class IntArray
{
private:
int* arr;
int arrLen;
public:
IntArray(int len) : arrLen(len)
{
arr = new int[len];
}
int& operator[](int index)
{
if (index < 0 || index >= arrLen)
{
cout << "Arr index out of range" << endl;
exit(1);
}
return arr[index];
}
~IntArray()
{
delete[] arr;
}
};
void main()
{
IntArray ia(3);
for(int i = 0; i <= 3; i++)
{
ia[i] = i + 1;
}
//결과 : Arr index out of range
}
- 결과와 같이 배열에 대한 잘못된 접근을 차단함으로써 안정성을 보장받을 수 있다.
※ 배열 클래스의 복사에 대해서..
- 배열은 저장소의 일종이고, 저장소에 저장된 데이터는 '유일성'이 보장되어야 하기 때문에, 저장소의 복사는 잘못된 일로 간주된다.
- 따라서, 복사 생성자와 대입 연산자는 private 멤버로 지정하여 복사를 원칙적으로 막는것이 좋은 선택이 되기도 한다.
//IntArray 클래스의 private에 복사 생성자와 대입 연산자를 멤버로 둔다.
IntArray(const IntArray& ref) { }
IntArray& operator=(const IntArray& ref) { }
const를 통한 배열 안정성 보장하기
- 어떠한 함수를 정의할 때, 매개변수를 const로 지정하면 해당 함수 내에서는 값의 변경이 불가능하므로 안정성을 보장받을 수 있지만, 위와 같은 코드에서는 인덱스 연산자에서 const 선언이 되어있지 않기 때문에 const로 지정된 매개변수로 받으면 컴파일 에러가 난다.
//전역함수
void ShowData(const IntArray& ref)
{
for(int i = 0; i < 3; i++)
{
cout << ref[i] << endl; //컴파일 에러 발생
}
}
- 해당 컴파일 에러를 해결하려면 const함수를 오버로딩하면 해결이 된다.
#include <iostream>
using namespace std;
class IntArray
{
private:
int* arr;
int arrLen;
IntArray(const IntArray& ref) { }
IntArray& operator=(const IntArray& ref) { }
public:
IntArray(int len) : arrLen(len)
{
arr = new int[len];
}
//배열 안에 값을 넣을 때 호출됨.
int& operator[](int index)
{
if (index < 0 || index >= arrLen)
{
cout << "Arr index out of range" << endl;
exit(1);
}
return arr[index];
}
//const 매개변수로 선언된 배열 안의 값을 찾을 때 호출된
int operator[](int index) const
{
if (index < 0 || index >= arrLen)
{
cout << "Arr index out of range" << endl;
exit(1);
}
return arr[index];
}
~IntArray()
{
delete[] arr;
}
};
void ShowData(const IntArray& ref)
{
for(int i = 0; i < 3; i++)
{
cout << ref[i] << endl;
}
}
void main()
{
IntArray ia(3);
for(int i = 0; i < 3; i++)
{
ia[i] = i + 1;
}
ShowData(ia);
//결과
//1
//2
//3
}
- 기존의 인덱스 연산자에서 const를 붙이는게 아닌 오버로딩을 한 이유는, 배열 멤버에 대한 저장이 불가능해지기 때문이다.
- 따라서 const 선언을 이용해 인덱스 연산자를 오버로딩하여 해결한다.
'Stack > C++' 카테고리의 다른 글
C++ 네임스페이스(namespace), using (0) | 2021.12.12 |
---|---|
C++ 연산자 오버로딩 (0) | 2021.12.05 |
C++ friend (0) | 2021.12.05 |
C++ 다중상속 (0) | 2021.11.22 |
C++ 멤버함수, 가상함수 동작원리 (0) | 2021.11.17 |