본문 바로가기

Archived(CSE Programming)/cpp

Chap 11. 연산자 오버로딩 2

Chap 11. 연산자 오버로딩 2 


11-1 반드시 해야하는 대입 연산자의 오버로딩.


연산자 오버로딩에 대해서 Chap 10에서 살펴봤다.

그 중에서 대입 연산자의 경우 operator= 에서 우리가 앞서 공부했던 복사 생성자와 같은 문제점을 지니고 있다.

바로 얕은 복사의 문제점이다.


default 대입 연산자의 경우 단순 멤버의 이동만 진행하여, 동적할당 된 같은 메모리 공간을 두 변수 이상이 가르키게 되는 문제점이 있다.

추가적으로 대입 연산자의 경우, 복사 생성자와 달리 기존의 값들이 존재하는 변수에다가 변수를 할당하는 것으로 기존의 동적할당 되어 있는 메모리 공간도 비어주어야 한다. 


1
2
3
4
5
6
7
8
// 복사 생성자의 경우
Point pos1(3,4);
Point pos2 = pos1; // Point(const Point & pos) 호출
 
// 대입 연산자의 경우
Point pos1(3,4);
Point pos2(5,6);
pos2 = pos1; //pos2.operator=(pos1); 
cs


따라서 operator=(const Point & pos) 에서 오버로딩 시 만약 동적할당이 되어있는 경우라면

반드시 기존의 동적 메모리 멤버 변수 공간을 해제해주고 새로이 할당해서 값을 옮겨서 깊은 복사가 이루어지도록 한다.


11-2. 배열의 인덱스 연산자 오버로딩


인덱스 연산자란 arr[2]와 같은 연산인데 이러한 인덱스 연산자도 마찬가지로 오버로딩이 가능하다.

단, 멤버함수로서만 가능한데 , 이 인덱스 연산자는 객체를 배열처럼 접근하겠다는 의미를 지니고 있다.

따라서 operator[](int) 의 오버로딩은 해당 주소값을 접근하여 해당 객체를 반환하는 연산으로 구현하면 다음과 같은 연산이 가능한 것이다.

Object obj[2] = 4; 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Point{
private:
    int xpos;
    int ypos;
public:
    Point(int x, int y) :xpos(x),ypos(y){}
    ...
};
 
class PointArray{
private:
    Point * arr;
    int arrlen;
public:
    PointArray(int l) :arrlen(l) {
        arr = new Point[l];
    }
    ...
}
 
void main(){
    PointArray parr(3);
    parr[0= Point(3,2);
    parr[1= Point(2,4);
    parr[2= Point(1,1);
}
 
cs


추가적으로, 객체의 저장을 위한 배열 클래스를 구현할 경우, 다음과 같이 우리는 객체를 저장할 배열을 할당해두고 인덱스에 접근하여 객체를 할당해주는 경우를 떠올린다. 

그러나, 이는 객체의 메모리 공간을 미리 할당해두고 필요할 때마다 공간을 할당하는 것이 아니라 객체를 생성해서 주소값을 연결하는 형태로 동적 메모리 할당의 의미를 담지 못한다.


따라서 우리는 다음과 같이 구현을 해야한다. 이차원 포인터로 객체의 주소값을 담을 수 있는 객체 포인터 배열을 멤버 변수로 가지고 있고 필요할 때마다 new 를 통해 메모리 공간을 할당하여 사용하여야 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Point{
private:
    int xpos;
    int ypos;
public:
    Point(int x, int y) :xpos(x),ypos(y){}
    ...
};
 
typedef Point * POINT_PTR;
 
class PointArray{
private:
    POINT_PTR * arr;
    int arrlen;
public:
    PointArray(int l) :arrlen(l) {
        arr = new POINT_PTR[l];
    }
    ...
}
 
void main(){
    PointArray parr(3);
    parr[0= new Point(3,2);
    parr[1= new Point(2,4);
    parr[2= new Point(1,1);
}
 
cs


11-3. 그 이외의 연산자 오버로딩


(1) new & delete


new 와 delete 키워드도 마찬가지로 오버로딩이 가능하다. 그렇지만 이 전의 연산자 오버로딩과 다른 형태로 진행되는데,

new의 경우 - 1_ 메모리 공간의 할당, 2_ 생성자의 호출, 3_주소값의 형 변환

이렇게 이루어지는데, 여기서 우리가 오버로딩할 수 있는 부분은 1번 메모리 공간의 할당 뿐이다.


즉, void * operator new(size_t size) 를 통해서 메모리 공간의 할당 부분만 오버로딩을 할 수있고 나머지 2,3 번은 컴파일러가 진행을 하게 된다. 매개변수와 void* 반환도 정해져 있다.


delete의 경우도 마찬가지다. 1_소멸자 호출, 2_ 메모리 공간의 해제

이렇게 이루어지는데, 여기서 우리는 2번 메모리 해제 부분만 오버로딩을 할 수 있다.


즉, void operator delete(void *) 를 통해서 메모리 공간의 해제 부분만 오버로디을 할 수 있다.


참고로, new[] 연산과 new 연산, delete [] 연산과 delete 연산 모두 각각 오버로딩이 가능하다.


(2) Pointer 연산자 오버로딩


Pointer 연산자 오버로딩은 * 연산자와 -> 연산자로 나누어진다.

각각 모두 포인터와 관련된 연산자로, operator*()의 경우 객체를 참조형으로 반환해주어야 하고,

operator->()의 경우 객체의 * 주소값을 반환해주어야 한다.


여기서 스마트 포인터란 개념이 존재하는데, 어려운 개념은 아니지만 class를 포인터처럼 사용하기 위해서 설계한 것들을 말한다.

가령, Point class에 대해서 SmartPoint란 class를 설계해서 멤버변수로 Point 객체의 주소값을 가지고 operator*()와 operator->()에 대해서 연산자 오버로딩 후, 그 이외의 것들을 추가적으로 제공할 수 있도록 사용하는 개념을 말한다.


(3) ()연산자 오버로딩과 펑터(Functor)


()연산자는 함수를 호출시 계속 사용하였던 연산자로, 이 자체를 오버로딩할 수 있다.

이 연산자를 오버로딩하면, 객체를 함수처럼 사용할 수 있다.

Adder adder; 선언 후 adder(3,4)과 같이 이러한 객체 자체를 함수로써 사용이 가능하다.


펑터(Functor)는 함수의 객체화인데, 함수 객체의 동작방식에 유연함을 제공해주는 것이다.


(4) 임시객체로의 자동 형 변환과 형변환 연산자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Number{
private:
    int num;
public:
    Number(int n):num(n){}
    Number(void):num(0){}
    ...
    // 형 변환 
    operator int(){
        return num;
    }
};
 
void main(){
    Number number1; // void 생성자로 객체 호출
    number1 = 3// 실제 동작 방식은 number1 = Number(3)
}
 
cs


다음과 같이 객체 자료형에다가 int형 자료형 값을 넣어도 알아서 객체를 만들어 대입연산을 진행하여 준다.

추가로 int()와 같은 형변환 연산자에 대해서도 오버로딩이 가능하다.

이 때, 반환 값을 따로 지정해주지 않아도 알아서 int 형으로 반환이 진행된다.