Hoon222y

[1220-3] 첨자 연산자 오버로딩/ 변환 연산자 오버로딩 (explicit) / 변환 생성자 본문

코딩/교육

[1220-3] 첨자 연산자 오버로딩/ 변환 연산자 오버로딩 (explicit) / 변환 생성자

hoon222y 2018. 12. 20. 20:38
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
38
39
40
41
42
43
44
45
46
//첨자 연산자 오버로딩
#include <iostream>
using namespace std;
 
class IntArray{
    int* buf;
    int size;
public:
    IntArray(int sz = 10): size(sz){
        buf = new int[size];
    }
    int length() constreturn size; }
    
    //아래의 코드가 동작될 수 있도록 함수를 변경하라.(리턴 타입을 int에서 int&로 바꿈)
    //첨자 연산자를 반환할 때는 참조 타입을 반환해야 하는데 그 이우는 값의 대입(=) 때문이다.
    int& operator[](int idx){                   //이 함수는 그냥 main문을 통해서 arr[i] 출력할 떄인데
        return buf[idx];                        //아래꺼는 print_arr를 밖에서 사용할 떄
    }
    
    //상수 객체는 상수 멤버 함수만 호출 가능하므로 위의 첨자 연산자를 사용할 수 없다.
    //이제 상수 객체용 첨자 연산자 함수를 구현한다.
    //구현해 보아라.  방법은 두가지 이다. 참조를 리턴하는것이 아니라
    //1. const int& 로 하던지 2.그냥 int 로 바꾼다. int& 자체가 상수이기 떄문에 상수에서 상수로 대입이 안됨.
    //여기서 주의할 점은 참조를 반환하여 값을 수정할 수 있고록 해서는 안된다는 것이다.
    // 따라서 살수(값)를 반환하도록 해야한다.
    int operator[](int idx) const {
        return buf[idx];
    }
};
 
void print_arr(const IntArray& arr){
    for (int i = 0; i < arr.length(); i++){
        cout << arr[i] << " ";
    }cout << endl;
}
int main(){
    IntArray arr(10);   //int arr[10];
    for (int i = 0; i < arr.length(); i++){
        //arr[i] = 0;           //이건 실행되지 않는다. 이것을 해결하는것이 첨자 연산자 오버로딩이다.
        //arr.operator[](i) = 0; //이렇게 하면 10=0 처럼 상수에 상수 대입이라 에러
        //arr.operator[](i) = 0;
        arr[i] = 0;             //이렇게 하기 위해서 함수의 리턴타입을 int& operator[]로 선언한다.
    }
    print_arr(arr);
}
 
cs


변환 연산자 오버로딩 : 변수에 값을 할당할 때 타입이 다른 경우가 있다. 이때 변환을 해주는 연산자를 사용하게 되는데 

operator 타입() 을 통하여 사용한다. 이때 주의할 점은 절대 return 타입을 명시하면 안된다는 점이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Complex{
    double r, i;
public:
    Complex(double real, double image) : r(real), i(image){}
    // 변환 연산자 함수 오버로딩 방법
    // operator 타입()
    // 주의! 단 절대 리턴 타입을 명시하면 안된다.
    operator double(){ return r; }
};
 
int main(){
    double d = 3.14;
    int i = (int)d;
    
    Complex c(11);                    //1+1i
    double real = (double)c;            //double(c) -> c.operator double();
    double real2 = double(c);
    cout << real << endl;
    cout << real2 <<endl;
}
cs


 여기서 double(c)나 (double)c 나 같이 동작한다. 하지만 8번째 줄처럼 구현하면 문제가 발생할 수 있다. 바로 암시적으로 변환이 되는 경우가 발생하기 때문이다. 

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Complex{
    double r, i;
public:
    Complex(double real, double image) : r(real), i(image){}
    // 변환 연산자 함수 오버로딩 방법
    // operator 타입()
    // 주의! 단 절대 리턴 타입을 명시하면 안된다.
    operator double(){ return r; }
};
 
int main(){
    double d = 3.14;
    int i = (int)d;
    
    Complex c(11);                    //1+1i
    double real = (double)c;            //double(c) -> c.operator double();
    double real2 = double(c);
    cout << real << endl;
    cout << real2 <<endl;
}
#endif
 
#if 0
#include <iostream>
using namespace std;
 
class Complex{
    double r, i;
public:
    Complex(double real, double image) : r(real), i(image){}
    
    /*
     //operator double(){ return r; }        //이걸 지우고
     
     //변환 연산자 대신 변환을 위한 인터페이스를 제공한다.
     //변환 연산자 사용하지 말고 왠만하면 변환 함수를 쓰는것이 더 낫다.
     //하지만
     double asReal(){ return r; }            //이건 사용자가 회피한거지 근본적 해결책이 아님
     */
    
    //explicit 키워드는 현재 변환 연산자를 명시적 변환에만 사용하고
    //암시적 변환에서는 사용하지 않겠다는 키워드이다.
    explicit operator double(){ return r; }
};
 
void print_double(double d){
    cout << d << endl;
}
 
int main(){
    Complex c(11);
    //print_double(c);              //이게 원래 double을 인자로 받는데 실행이 된다. 왜일까 ?
    //operator double(){ return r; }이걸 통해서 된 것 작동
    //변환 연산자 암시적 변환에 사용!
    //해당코드는 만약에 explicit으로 선언될 경우 암시적 변환이 안되기 떄문에 에러 발생.
    //cout << c.asReal() << endl;
    print_double(double(c));        //explicit을 사용하여 암시적 변환에 사용하지 않고 명시적 변환에서만 사용 한다고
    print_double((double)c);        //둘다 가능.
}
cs


52번째 줄에서 print_double 의 경우 인자로 double을 받는데, Complex 객체를 입력 받아도 작동이 된다. 이것은 operator double(){ return r; }    의 수행으로 인하여 컴파일러 내에서 암시적으로 변환이 된 것이다. 이러한 문제를 피하기 위해서 변환 연산자가 아닌 변환 함수를 사용하묜 된다. 하지만 이것은 사용자가 피한것이지 근본적인 해결책이 되지는 않는다. 따라서 이 때 사용할 수 있는것이 바로 explicit 키워드이다. 

 explicit 키워드 : 현재 변환 연산자를 명시적 변환에서만 사용하고, 암시적 변환에 사용하지 않겠다는 뜻이다. 이 때 사용하기 위해서는 46번째 줄에서 매개변수가 double 로 들어가는데 Complex 클래스 내에서 operator double 변환 연산자를 통하여 return r이 되도록 하는것이다. 이때 double(c) 나 (double)c 둘다 똑같이 작동한다. 


변환 생성자 


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
//변환 생성자
//인자 1개를 사용하여 생성자를 호출할 수 있는 생성자는 모두 변환생성자가 될 수 있다.
#include <iostream>
using namespace std;
//결론 ! 변환생성자와 연산자를 사용할 때는 가급적 explicit을 사용하자
class Int32{
public:
    int val;
    // 아래와 같은 생성자가 암묵적으로 사용되지 않도록 하려면 생성자의 앞에 explicit 키워드를 사용하면 된다.
    explicit Int32(int v = 0) :val(v){
        cout << "int32(" << v << ")" << endl;
    }
};
void print_int32(Int32 i){
    cout << i.val << endl;
}
 
int main(){
    Int32 i(10);
    print_int32(i);
    int a = 20;
    //print_int32(a);               //이게 되는것을 의도한것은 아닌데 된다.
    //컴파일러가 생성자를 print_int32(Int32(a)) 이렇게 해서 넘겨준 것이다. 그래서 동작함.
    //결과값을 수행해보면 이때 생성자가 발생하는 것을 볼 수 있다.
    //생성자에 explicit 쓰면 이러한 암시적 변환을 막을 수 있다.
    
    Int32 i2(10);
    // Int32 j2 = 10;       //Int32 j = Int32(10;   -> Int32j(Int32(10))
    //explicit 으로 막아서 작동을 안하게 된다.
}
cs


 해당 코드들을 보면 22번 째 줄에서 print_init32(a)가 암시적 형변환으로 실행이 된다. 인자로 들어간 10이 10번째 줄에서 변환생성자가 되어 인자로 Int32 타입으로 형변환이 되는것이다. 이러한 암시적 형변환을 막기 위해서 explicit을 선언한다. 28번째 줄 또한 10이 변환 생성자로 인하여 Int32 타입으로 자동 형변환 되어 사용되는 것이다. 이 모든것을 막기위해 explicit을 선언한다. 변환 생성자와 연산자를 사용할 경우에는 가급적 explicit을 사용한다. !! 

Comments