Hoon222y

[1220-2] 연산자 오버로딩 / 임시 객체 / 출력 연산자 오버로딩 본문

코딩/교육

[1220-2] 연산자 오버로딩 / 임시 객체 / 출력 연산자 오버로딩

hoon222y 2018. 12. 20. 19:57

연산자 오버로딩 : 클래스에 대한 연산자를 정의하여 t3 = t2+t1; 처럼 객체끼리 연산할 수 있도록 만드는 문법이다. 

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
30
31
#include <iostream>
using namespace std;
class Int32{
    int v;
public:
    Int32(int val = 0) : v(val){}
    void print() constcout << v << endl; }
    int get() constreturn v; }
 
    Int32 operator+(const Int32& o){
        Int32 temp(v + o.v);
        return temp;
    }
};
 
Int32 add(const Int32& i, const Int32& j){
    Int32 temp(i.get() + j.get());
    return temp;
}
 
int main(){
    Int32 i(10);            // == Int32 i = 10;
    Int32 j(20);
    
    Int32 k = i + j;        //이걸 연산자 오버로딩한다.
    //i.operator+(j);       자기자신과 동일한 타입을 받는다.
    //Int32 k = add(i,j);
    k.print();
    Int32 x = i.operator+(j);//이런식으로 명시적으로 호출 또한 가능하다.
    x.print();
}
cs


 코드를 보면서 이야기 하자. 원래라면 25번째 줄처럼 연산을 하기 위해서는 + 기호를 사용하는 것이 편하다. 하지만 +라는 기호가 각각의 클래스마다 어떻게 더해질 지 모르기 때문에 사용자가 해당 연산자에 대하여 재정의 하는것이다. 

 i와 j를 더하는 방법은 1.  16~18줄 처럼 클래스 내부에 멤버 변수 값을 가져오는 함수를 정의하고, 임시 temp 변수를 만들고 해당 변수를 return 하는 방법과 2. + 연산자에 대해 재정의 하는 방법이 있다. 연산자 오버로딩의 경우 리턴타입 함수 이름([매개변수,..]) 로 구성이 되어있다. 

 이 때 살펴볼 점은 25번 째 줄처럼 +연산자 오버로딩을 사용하여 정의할 수도 있고, 29번째 줄처럼 명시적으로 호출이 가능하다는점이다.  연산자 오버로딩은 멤버 함수 뿐만 아니라 비멤버 함수로도 구현이 가능하다. 따라서 

1
2
3
4
5
6
7
8
//비멤버 버전의 연산자 오버로딩을 좀 더 쉽게 구현하기 위해 friend
friend Int32 operator+(const Int32& i, const Int32& j);
//class내부에 선언 후 
 
Int32 operator+(const Int32& i, const Int32& j){
    Int32 temp(i.v + j.v);
    return temp;
}

cs


 해당 코드처럼 class 내부에 friend 키워드를 붙이고 멤버 변수를 직접적으로 사용할 수 도 있다. 비멤버 버전의 연산자 함수가 필요한 경우는 여러가지가 있지만 대표적으로는 교환법칙을 지원하기 위해서다. 

 참고적으로 연산자 함수도 오버로딩이 가능하다. 

1
2
3
4
5
Int32 x = i + 10;         // i.operator+(10)
x.print();
    
Int32 y = 10 + i;           
y.print();
cs

 

해당 경우처럼 i+ 10 , 10 + i에 대한 교환법칙을 성립시키기 위해서 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    Int32 operator+(const Int32& o){
        //Int32 temp(v + o.v);                  //5번 코드 보고 이후에 주석처리 하였음
        //return temp;
        return Int32(v + o.v);
    }
    //연산자 함수도 오버로딩이 가능하다.
    Int32 operator+(int i){
        //Int32 temp(v + i);
        //return temp;
        return Int32(v + i);
    }
    friend Int32 operator+(int i, const Int32& j);
};
 
Int32 operator+(int i, const Int32& j){
    //Int32 temp(i + j.v);
    //return temp;
    return Int32(i + j.v);
}
cs


해당처럼 각각의 경우에 대해 각각의 연산자 오버로딩을 가능하게 한다. 


지역 객체 : 이름이 있으며, 스텍이 파괴될 때 삭제된다. 

임시 객체 : 이름이 없으며, 명령이 끝날 때 삭제된다. 

1
2
3
4
5
6
7
8
9
10
11
int main(){
    Int32 i(10);              //지역 객체: 이름이 있음, 스텍이 파괴될 때 삭제됨
    Int32(20);                //임시 객체: 이름이 없음, 명령이 끝날 때 삭제됨
    getchar();
    
    int i1 = 10;
    int j1 = 20;
    //임시 객체는 컴파일러의 필요에 의해 생성되는 개념이다.
    int k = i1 + j1;              //여기서 i,j가 임시객체라고 볼 수 있다.
    //int temp = i+j; int k = temp; delete temp;랑 같은 것이다.
}
cs


해당 코드와 같이 9번째 줄에서 볼 수 있듯 연산이 진행되면 temp 임시변수가 발생할 수 있다. 따라서 어떤 함수 내에서 자신의 타입을 만들자 마자 반환하게 된다면 임시객체를 생성해서 반환하는 것이 좋다. 지역 객체를 생성하면 임시 객체가 생성되고, 값을 복사한 뒤에 파괴되기 때문에 성능상에 오버헤드가 발생하기 때문이다.따라서 

1
2
3
4
5
6
7
8
    Int32 operator+(const Int32& o){
        //Int32 temp(v + o.v);
        //return temp;
        //위에 주석처리한것처럼 해도 되는데 아래처럼 선언하면
        return Int32(v + o.v);      //라고 하면 임시객체를 만들자마자 리턴한다.
    }                               //RVO(Return value optimazation) 이라고 한다.
    //최신의 컴파일러는 최적화를 통하여 임시 객체를 사용하지 않고도 RVO를 수행하는데
    //이를 NRVO(named Return value optimazation)라고 한다.
cs


처럼 바로 임시객체를 생성한 후 반환한다. 이를 RVO라고 하고, 최신 컴파일러의 경우 임시 객체를 생성하지 않고도 RVO를 수행하는데 이를 NRVO라고 한다. 



출력 연산자 오버로딩 : << 

 

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
30
31
32
33
34
35
36
37
class Person{
    char name[32];
public:
    Person(const char* name) {
        strcpy(this->name, name);
    }
    friend ostream& operator<<(ostream& coutconst Person& o);
};
 
//namespace std{
//  class ostream{};
//  ostream cout;
//} 이므로 아래에서 ostream& cout 이다.
 
//여기서 name을 사용하기 위해서 방법 2가지
//1. 친구를 한다. 2. class 내부에서 get name함수를 만들어서 가져온다.
//출력 연산자를 구현하는 방법
//1. 아래 함수를 구현한다.
//2. 그리고 cout 객체를 반환하도록 해야한다.  자기자신을 리턴하여 다양한 타입에 대해서 연쇄적 출력을 위하여
 
ostream& operator<<(ostream& c, const Person& o){
    c << o.name <<endl;
    return c;
}
int main(){
    Person p("hoon");
    cout << p;
    //원래 void operator<<(ostream& cout, const Person& o)일 떄는
    //cout << p 만 되고 cout<< p << endl;가 안된다.
    //하기 위해서는 return 타임을 osream&로 해서연속으로 호출이 되게 한다.
    
    cout << p << endl;
    //cout.operator<<(p)랑 같은 것이다. 원래는 위에것만 사용 가능하지만
    // ostream& 을 해서 문제를 해결하였다.
    //cout 안에는 operator << 이있긴 하지만 그건 Person 타입이 아니다.
    //맴버 함수가 없기때문에 비맴버 함수를 찾는다. -> operator<<(cout,p)
}
cs


 출력 연산자 오버로딩의 경우 주의할 점은 

1. 연속적인 호출을 하기 위해서 return 타입이 ostream& 이라는 점(이를 통해서 <<endl; 이 가능해짐 )

2. 안에 매개변수로 들어가는 것이 (ostream& c, 클래스& 변수명) 

3. 클래스 안에 들어가는 변수에 접근하고 싶다면 friend 키워드 선언 이다. 

Comments