사용자가 직접 정의해서 사용할 수 있는 타입 변환
은 두 가지가 있다.
생성자
를 이용한 타입 변환.타입 변환 연산자 오버로딩
을 이용한 타입 변환.특정 타입을 인자로 받는 생성자가 있다면 생성자 호출을 통한 타입 변환(객체 생성 후 대입)이 가능하다. 생성자를 이용해 다른 타입을 자신의 타입으로 변환할 수 있다.
#include <iostream>
using namespace std;
class A
{
};
class B
{
public:
B() { cout << "B() 생성자" << endl; }
B(A& _a) { cout << "B(A& _a) 생성자" << endl; }
B(int n) { cout << "B(int n) 생성자" << endl; }
B(double d) { cout << "B(double d) 생성자" << endl; }
};
int main()
{
A a;
int n = 10;
double d = 5.5;
B b; // B() 생성자 호출
b = a; // b = B(a) 암시적 생성자 호출 후 대입
b = n; // b = B(n) 암시적 생성자 호출 후 대입
b = d; // b = B(d) 암시적 생성자 호출 후 대입
return 0;
}
/* 결과
B() 생성자
B(A& _a) 생성자
B(int n) 생성자
B(double d) 생성자
*/
b = a
에서 컴파일러
는 A 타입
의 객체를 B 타입
으로 변환
하기 위해 생성자
를 확인한다. A 타입
의 객체(a)를 인자로 받는 생성자가 있으므로 이 생성자를 호출해 B 타입
의 객체를 생성한다.
아래의 Point
객체에 정수를 대입한다면 컴파일이 가능할까? 정답은 가능하다
이다. 그 이유는 정수를 인자로 받는 생성자가 있기 때문이다.
#include <iostream>
using namespace std;
class Point
{
public:
Point(int x = 0, int y = 0) : x(x), y(y) {}
void Print() const{ cout << x << ", " << y << endl; }
private:
int x;
int y;
};
int main()
{
Point pt;
pt.Print();
pt = 10; // Point(10, 0) 암시적 생성자 호출
pt.Print();
return 0;
}
pt = 10
에서 정수를 인자로 받는 생성자
가 있으므로 컴파일러
는 Point(10, 0)
을 호출해 Point 객체
를 생성
한다. 이렇게 하면 실수로 Point 객체
에 정수를 대입해도 컴파일이 성공하여 버그로 연결된다.😱
그래서 생성자를 이용한 형변환을 의도하지 않는다면, 생성자는 명시적 호출만 가능하도록 explicit
키워드를 지정한다.
다음은 Point 생성자에 explicit
키워드를 지정한 예이다.
#include <iostream>
using namespace std;
class Point
{
public:
explicit Point(int x = 0, int y = 0) : x(x), y(y) {}
void Print() const{ cout << x << ", " << y << endl; }
private:
int x;
int y;
};
int main()
{
Point pt;
pt.Print();
// pt = 10; // 에러! 암시적 생성자 호출 불가능.
pt = Point(10); // 명시적 생성자 호출만 가능!
pt.Print();
return 0;
}
explicit 생성자
는 명시적
호출만 가능하므로 Point(10)
과 같이 호출한다.
📌 암시적인 생성자 형 변환을 의도하지 않는 한, 인자를 갖는 생성자는 모두 explicit 생성자로 만들자!
타입 변환 연산자 오버로딩
을 사용하면 자신의 타입을 다른 타입으로 변환
할 수 있다.
#include <iostream>
using namespace std;
class A
{
};
class B
{
public:
operator A()
{
cout << "operator A() called" << endl;
return A();
}
operator int()
{
cout << "operator int() called" << endl;
return 55;
}
operator double()
{
cout << "operator double() called" << endl;
return 3.14;
}
};
int main()
{
A a;
int n;
double d;
B b;
a = b; // b.operator A() 암시적 호출
n = b; // b.operator int() 암시적 호출
d = b; // b.operator double() 암시적 호출
cout << endl;
a = b.operator A(); // 명시적 호출
n = b.operator int(); // 명시적 호출
d = b.operator double(); // 명시적 호출
return 0;
}
/* 결과
operator A() called
operator int() called
operator double() called
operator A() called
operator int() called
operator double() called
*/
a = b
에서 컴파일러
는 B타입 객체(b)
를 A 타입
으로 변환하기 위해 타입 변환 연산자 b.operator A()
멤버 함수를 호출한다.
❗주의할 점은
타입 변환 연산자
는생성자
나소멸자
처럼반환 타입
을 지정하지 않는다.
다음의 예제는 Point 클래스 타입을 정수 타입으로 변환할 수 있게 타입 변환 연산자
를 오버로딩
한다.
#include <iostream>
using namespace std;
class Point
{
public:
explicit Point(int x = 0, int y = 0) : x(x), y(y) {}
void Print() const { cout << x << ", " << y << endl; }
operator int() const
{
return x;
}
private:
int x;
int y;
};
int main()
{
int n = 10;
Point pt(2, 3);
n = pt; // pt.operator int() 암시적 호출
cout << n << endl;
return 0;
}
/* 결과
2
*/
n = pt
는 pt.operator int() const
멤버 함수를 호출해 pt
의 x
값을 반환한다. const 객체
나 비 const 객체
모두 타입 변환이 가능하게 const 멤버 함수
로 정의한다.