변환시리즈 정리(변환생성자, 변환함수)


일단 어려운 단어가 나왔다고 쫄지 말자.
변환시리즈
즉 변환연산자와 변환함수는 변환을 시켜주는 생성자와 함수이다.

정의를 들어가기전에 
언제나 그렇듯이  프로그래밍언어는 뒤에 강세를 두고 읽으면 좀 쉽다.
변환생성자는 결국은 생성자이고,
변환함수는 결국은 함수인것이다.

그럼 이것이 무었이냐~ 정의를 내려보면 
기본형과 사용자 정의 타입(클래스)간에 연산을 할때
(클래스->기본형) 또는 (기본형->클래스) 간의 형변환을해주는 것을 말한다.

결론부터 얘기하면
변환생성자는 기본형->클래스로의 변환이고,
변환함수는 클래스->기본형으로의 변환을말한다.

 
1.변환 생성자


반 타입의 변수끼리 값을 대입할 때는 산술 변환 규칙에 따라 암시적으로 상호 변환된다. 물론 모든 타입들이 다 상호변환되는 것은 아니며 호환되는 타입들끼리만 그렇다. 다음의 코드를 보자.

int i = 'C';

double d = 12;

'C'는 문자형 상수이지만 정수형 변수 i에 대입할수있으며 12는 정수형 상수지만 실수형 변수 d에 대입할 수 있다.

그 이유는,

문자형 -> 정수형 변환시에는 암시적으로 상승변환되며

반대일경우엔 하강변환 된다.

정수형 변수에 실수형 값을 대입하는 식의 하강 변환인 경우 약간의 정확도 손실이 발생할수가 있다.

일단, 클래스의 객체들도 일반 타입과 마찬가지로 암시적 변환이 가능할 수 있는데 클래스가 일반 타입과 완전히 동등해 지려면

물론 그 객체를 변환할수 있는 문법적 장치가 있어야 한다. 그 첫번째 장치가 바로 변환 생성자(Conversion Constructor)이다.

(변환생성자는 기본 타입으로부터 객체를 만드는 생성자이며 인수를 하나만 취한다. 인수가 둘 이상이면 변환생성자가 아니다.)



 #include <iostream>

using namespace std;

class Time {
private:
 int hour, min, sec ;
public:
 Time() { }
 Time(int abssec) {
  hour = abssec/3600;
  min = (abssec/60)%60;
  sec = abssec%60;
 }
 void OutTime() { cout<<"현재 시간은"<<hour<<":"<<min<<":"<<sec<<"입니다."<<endl; }
};

int main() {
 Time Now(3723);
 Now.OutTime();
 
 return 0;
}

 

지금 여기서는 두개의 생성자가 정의되어있다.

무엇인것 같은가 ??

디폴트생성자와 변환 생성자 두개의 생성자가 정의되어 있다.

 

 

 

변환생성자에 대해 설명하자면,

Time(int) 생성자는 정수형 abssec 인수 하나만을 취하는데 절대초 abssec으로부터 시, 분, 초 를 구해 객체를 초기화 한다.

따라서 정수값 하나를 변환하여 객체를 생성하는 것이다. 이런 생성자를 변환 생성자라고 한다.

main 함수의 첫번째 문장 Time Now(3723); 은 정수 상수 3723이라는 값으로 부터 1:2:3 초라는 Time형 객체를 변환 생성한다.

 

여기서 변환생성자의 문제점에 대해 한가지 예를 들어 보겠다.

여기서는 Time 객체를 Now(3723)으로 호출하지만, 만약에 의도된 호출이 아닌 단순한 실수로 Now('S')라던가 Now(123,345) 같은 호출문을 작성해버린다면 에러로 처리되지 않는다. 문자형이나 실수형은 정수형으로 암시적 변환이 가능하과 이렇게 변환된 정수형은 다시 변환 생성자에 의해 Time형 객체로 변환이 가능해져버리기 때문이다. 따라서 변환생성자가 편리하기도하지만 클래스와 일반 타입간의 구분이 모호해져버린다는 문제점이 발생한다....

 

이를 해결해주기 위하여 explicit 키워드를 변환생성자 앞에 붙인다.



class Time {

private:

int hour, min, sec;

public:

explicit Time(int abssec)....

....

}

 

explicit로 지정된 생성자는 암시적인 형변환에 사용할 수 없도록 금지된다. 즉, 컴파일러가 임의적인 판단을 하지 못하도록 한다.

그러나 명시적인 형변화이나 캐스트 연산자를 쓰는것은 여전히 가능하다.

 

Time Now=3723; //불가능

Time Now(3723); //가능

Time Now=(Time)3723; //가능 <- 명시적인 생성자 호출이나 캐스트 연산자는 사용자가 변환하라는 의사를 분명히 밝힌 것이므로 explicit 키워드와는 상관없이 허용 된다. 

 

그리고 마지막으로 정리를 하자면

 

변환생성자는

반드시 인수를 하나만 취해야 한다. ( 둘이상의 인수를 취할 경우 변환 생성자가 아니다.)

왜냐하면 변환이란 일대일 연산이기 때문이다.

변환 생성자가 적용 되는 초기화, 대입 연산은 이항 연산을 하는데 객체 자신으로 정해져 있으므로 나머지 우변이 되는 변환 대상에 대해서만 인수를 전달 받아야 한다. 단, 복사생성자는 인수를 하나만 취하지만 동일 타입으로부터 사본을 생성하므로 변환 생성자라고는 할수가 없다. 


2.변환함수

변환 생성자를 정의하면 정수값으로부터 Time형 객체를 만들 수 있고 Time형 객체에 정수값을 대입할 수도 있다. 이것이 가능하다면 반대의 변환, 즉 Time형 객체로부터 정수값을 만들어내는 것도 가능할 것이다. 정수가 Time이 될 수 있다면 Time도 정수가 될 수 있어야 비로소 두 타입이 완전히 호환된다고 표현할 수 있다. 다음 코드가 제대로 동작해야 한다.

 

Time Now(18,25,12);

int i=Now;

printf("i=%d\n",i);

 

18:25:12초라는 시간이 절대초로 얼마인가를 계산한 후 정수값으로 출력해 보는 코드이다. 그러나 이 코드는 아직 동작하지 않는다. 왜냐하면 Time 클래스는 정수를 Time으로 바꾸는 변환 생성자만 제공할 뿐 자신을 정수로 바꾸는 방법은 제공하지 않기 때문이다. 객체를 일반 타입으로 역변환하려면 변환 함수(Conversion Function)를 정의해야 한다. 변환 함수의 형식은 다음과 같다. 다음에 상세하게 알아보겠지만 변환 함수는 캐스트 연산자에 대한 오버로딩의 한 예이다.

 

operator 변환타입()

{

          본체

}

 

키워드 operator 다음에 변환하고자 하는 타입의 이름을 밝히고 본체에는 변환 방법을 작성한다.
변환 함수는
인수를 취하지 않으며 리턴 타입도 지정하지 않는다. 왜냐하면 연산 대상은 자기 자신으로 고정되어 있고 변환 결과는 지정한 타입임을 이미 알고 있기 때문이다. 객체 자신을 다른 타입으로 변환하는 동작을 하므로 작업거리와 결과가 이미 정해져있는 것이다. Convert1 예제의 Time 클래스에 시분초를 전달받는 생성자와 변환 함수를 추가해 보자.

 

  : Convert2

#include <Turboc.h>

 

class Time

{

private:

     int hour,min,sec;

public:

     Time() { }

     Time(int abssec) {

          hour=abssec/3600;

          min=(abssec/60)%60;

          sec=abssec%60;

     }

    Time(int h, int m, int s) { hour=h; min=m; sec=s; }

    operator int() {

        return hour*3600+min*60+sec;

    }

     void OutTime() {

          printf("현재 시간은 %d:%d:%d입니다.\n",hour,min,sec);

     }

};

 

void main()

{

     Time Now(18,25,12);

     int i=Now;

     printf("i=%d\n",i);

}

 

operator int() 변환 함수가 Time 클래스의 멤버 함수로 작성되어 있다. 변환 함수의 본체는 아주 단순한데 시간에 3600을 곱한 값, 분에 60을 곱한 값, 그리고 초를 모두 더하면 절대초를 쉽게 계산할 수 있으며 이렇게 구한 정수값을 리턴한다. 변환 함수의 원형에 리턴 타입이 없지만 어디까지나 생략된 것일 뿐이므로 본체에서는 return 문을 사용할 수 있다. 변환 함수에 의해 객체를 int로 변환할 수 있는 방법이 정의되었으므로 이제 Time형 객체는 정수형 변수에 대입될 수 있다.

main에서 Time형 객체 Now를 18:25:12초로 초기화하고 정수형 변수 i를 Now로 초기화했다. 이때 변환 함수가 호출되어 Now객체의 멤버값으로부터 절대초를 계산하여 리턴할 것이며 i는 그 결과값을 가진다. 출력되는 결과는 18:25:12초의 절대초인 66312가 된다. int와 호환된다는 것은 사실상 모든 수치형과 호환될 수 있다는 뜻이며 Time형 객체는 char, double, float, long 등의 타입과도 상호 변환 가능하다.

변환 함수와 변환 생성자는 하는 일이 비슷하기 때문에 닮은 점이 많다. 우선 변환 생성자와 마찬가지로 변환 함수도 필요한만큼 얼마든지 정의할 수 있다. Time 객체를 실수나 문자형으로도 변환하도록 하고 싶다면 operator double(), operator char() 함수를 더 정의하면 된다. 또한 변환 함수도 변환 생성자와 똑같은 이유로 다소 위험한 면이 있다.

 

void func(int i)

{

     ....

}

 

func 함수는 정수형 인수 하나를 받아 들이는데 이 함수에 대해 func(Now)로 호출할 수도 있다. 왜냐하면 변환 함수에 의해 Time형 객체가 정수로 변환될 수 있기 때문이다. 의도적인 호출이라면 물론 변환 함수의 서비스를 기분좋게 받겠지만 단순한 실수일 경우는 문제가 커진다. 다음 예를 자세히 살펴보자.

 

int Nox,Noy;

Time Now;

gotoxy(Nox,Now);

 

gotoxy의 두 번째 인수는 필시 Noy를 잘못 적은 것이겠지만 문법적으로 적법하며 컴파일러는 이 문장이 뭐가 문제인지 알 리가 없다. Now가 정수가 될 수 있으므로 gotoxy의 y좌표로 사용한다 하더라도 뭐 이상할게 없는 것이다. 심지어 ar[Now]=0; 같이 배열의 첨자에도 Time형 객체를 쓸 수 있으며 ptr+Now같이 포인터에 Time형 객체를 더하는 것도 허용된다. 변환 함수가 있으니 컴파일러는 이런 심히 수상해 보이는 코드에 대해서 경고 하나도 발생하지 않을 것이다.

게다가 변환 함수는 변환 생성자처럼 explicit로 암시적 변환을 금지하는 장치도 없어 주의 깊게 사용하는 수밖에 없다. 암시적 변환이 정 문제가 된다면 아예 변환 생성자나 변환 함수를 만들지 말고 TimeToInt, IntToTime 같은 명시적인 함수를 만들어 꼭 필요할 때만 사용하는 편이 더 안전하다.

 

  : Convert3

#include <Turboc.h>

 

class Time

{

private:

     int hour,min,sec;

public:

     Time() { }

     Time(int h, int m, int s) { hour=h; min=m; sec=s; }

     void OutTime() {

          printf("현재 시간은 %d:%d:%d입니다.\n",hour,min,sec);

     }

     int TimeToInt() {

          return hour*3600+min*60+sec;

     }

     void IntToTime(int abssec) {

          hour=abssec/3600;

          min=(abssec/60)%60;

          sec=abssec%60;

     }

};

 

void main()

{

     Time Now(18,25,12);

     int i=Now.TimeToInt();

     printf("i=%d\n",i);

 

     Time Now2;

     Now2.IntToTime(i);

     Now2.OutTime();

}

 

이렇게 되면 변환이 필요할 때 사용자가 멤버 함수를 명시적으로 호출해야만 하며 컴파일러가 어떠한 변환 서비스도 하지 않으므로 좀 불편하기는 하지만 최소한 위험하지는 않다.

출저: www.winapi.com


 

자주 헷갈리는 변환시리즈를 정리했다 

 역시 진리의 혼연을 보며정리함 

 

이 글을 공유하기

댓글

Designed by JB FACTORY