변환 생성자란 별 다른건 아니고 그냥 생성자는 생성자인데 기본 타입 1개를 인자로 받는 생성자이다.
#include <iostream>
using namespace std;
class Distance
{
private:
int kilometer, meter;
public:
Distance() : kilometer(0), meter(0){}
Distance(int newDist) // 변환 생성자
{
kilometer = newDist / 1000;
meter = newDist % 1000;
}
void PrintDistance()
{
cout << "Distance is " << kilometer << "km " << meter << "m\n";
}
};
위의 예시에서는 Distance(int newDist) 가 변환 생성자이다.
다른 예시를 들어보자.
#include <iostream>
#include <cassert>
using namespace std;
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0); // den 이 0이 아니면 true => pass / den이 0이면 false => assert error
}
Fraction(const Fraction &fraction)
:m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
{
cout << "Copy constructor called" << endl;
}
friend std::ostream & operator << (std::ostream & out, const Fraction & f)
{
out << f.m_numerator << " / " << f.m_denominator << endl;
return out;
}
};
void doSomething(Fraction frac)
{
cout << frac << endl;
}
int main()
{
doSomething(7);
return 0;
}
여기서 생성자는
- Fraction( int num = 0, int den = 1 ) 이다.
- 생성자
- 인수 0개 받으면 👉 Fration(0, 1)
- 인수 1개 받으면 👉 Fration(받은 인수, 1)
- 인수 2개 받으면 👉 Fration(받은 인수, 받은 인수)
- void doSomething(Fraction frac)
- 이 함수는 Fraction 타입의 객체를 인수로 받는다.
doSomething(7); // 문제 없음! doSomething(Fracion(7, 1)); 이나 마찬가지다.
- 위 함수는 Fraction 타입의 객체를 인수로 받는데도 불구하고 int인 7을 인수로 넘겨도 아무 문제가 없다.
- 7이 인자로 넘겨질 때 임시 객체인 frac이 생성되면서 Fraction frac = 7
- 즉 Fraction(int num = 0, int den = 1) 생성자가 호출되어 Fraction 타입의 임시 객체가 만들었졌기 때문에 문제가 없는 것이다.
- 인자로 7 하나만 받았기 때문에 den 멤버는 디폴트 값은 1로 초기화되어 최종적으로 “Fracion(7, 1)”로 호출한 것이나 마찬가지인 Fraction 타입의 임시객체가 생성된다.이처럼 기본 자료형 1개를 인수로 받는 생성자는 컴파일러가 자동 형변환을 해주기 때문에 이러한 특이 현상이 일어날 수 있다. 이런 현상이 생기는 생성자를 변환 생성자라고 한다.
- 인수를 int 로 잘못 넘겼음에도 불구하고 컴파일러가 int 하나를 받는 생성자를 호출하여 Fraction 타입의 임시 객체로 자동 형변환 해준 셈.
- 위 생성자는 int 인수 하나만 들어오면 무조건 OK라 이런 문제가 발생함.
- 즉 Fraction(int num = 0, int den = 1) 생성자가 호출되어 Fraction 타입의 임시 객체가 만들었졌기 때문에 문제가 없는 것이다.
- 7이 인자로 넘겨질 때 임시 객체인 frac이 생성되면서 Fraction frac = 7
변환 생성자의 좋지 않은 점
- 논리성이 떨어진다.
- 특정 타입의 객체를 인수로 받는다고 명시해놨음에도 불구하고 컴파일러가 자동 형변환을 해줘서 int 로 넘겨도 문제가 없으니까!
- 실수로 int 로 변환될 수 있는 char 같은 것을 넘겨도 컴파일러가 자동 형변환을 해주므로 변환 생성자가 호출되는 문제가 발생한다.
- 메모리 낭비다.
- int로 넘겨도 변환 생성자를 호출하는 바람에 임시 객체를 생성하고 소멸하는 과정이 따르기 때문.
이러한 문제 때문에
explicit 을 사용한다.
explicit
생성자 앞에 explicit 키워드를 붙여주면 변환 생성자의 무작위 호출을 막고 명확성을 높혀준다.
explicit Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0);
}
doSomething(7); // 💥컴파일러 에러 발생
인수로 넘긴 7이 Fraction(7, 1)로 호출되는 것을 막고 컴파일러 에러를 발생시킨다. 이제 doSomething(Fraction(7, 1)); 혹은 doSomething(frac); 하고 타입을 명확히 해서 호출해주어야 한다.
delete
생성자에 delete 키워드를 붙여 특정 타입의 인수는 받지 않도록 막을 수도 있다.
Fraction(const Fraction & frac) = delete;
- 복사 생성자 구현안한 상태인데 디폴트 복사 생성자를 호출하고 싶지 않다면 위와 같은 방식으로 호출을 막을 수도 있다.
Fraction(char) = delete;
...
Fraction frac('c'); // 💥컴파일러 에러 발생
- 인수 1개를 받는 생성자 호출시 char 타입의 인수는 받지 않도록 막았다.
#include <iostream>
#include <cassert>
using namespace std;
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction(char) = delete;
explicit Fraction(int num = 0, int den = 1)
: m_numerator(num), m_denominator(den)
{
assert(den != 0);
}
Fraction(const Fraction &fraction)
:m_numerator(fraction.m_numerator), m_denominator(fraction.m_denominator)
{
cout << "Copy constructor called" << endl;
}
friend std::ostream & operator << (std::ostream & out, const Fraction & f)
{
out << f.m_numerator << " / " << f.m_denominator << endl;
return out;
}
};
void doSomething(Fraction frac)
{
cout << frac << endl;
}
int main()
{
doSomething(7); // 💥컴파일러 에러 발생
Fraction frac('c'); // 💥컴파일러 에러 발생
return 0;
}
https://ansohxxn.github.io/cpp/chapter9-11/
'Language & etc > C++' 카테고리의 다른 글
std::Move, perfect forwarding - ① (0) | 2022.06.19 |
---|---|
전방 선언 (Forward Declaration) #22.11.22일 복습 (0) | 2022.06.10 |
LValue, RValue , 이동 생성자 깊게 이해하기 (feat.Copy Elision ) (0) | 2022.05.04 |
타입 변환 연산자 (0) | 2022.04.17 |
const class ( 상수 클래스 ) (0) | 2022.04.17 |