C++/C++

변환 생성자와 묵시적 형변환(작성 중)

Elan 2021. 3. 4. 04:22

변환 생성자

변환 생성자는  기본 타입을 이용해서 객체를 생성하는 생성자이며,

매개변수가 하나인 생성자를 말한다.

 

이 변환 생성자라는 녀석은 여러 가지 불상사를 낳는다.

class Test {
public:
	Test() 
	{
		cout << "Test()" << endl;
		pData = new int(0);
	}

	Test(int _data)//변환 생성자
	{
		cout << "Test(int)" << endl;
		pData = new int(_data);
	}

	Test(const Test& rhs) // 복사생성자 ( Copy COnstructor )
	{
		cout << "CTest(const Test &)" << endl;
		pData = new int(*rhs.pData);
	}
	~Test()
	{
		cout << "~CTest()" << endl;
		delete pData;
	}

	int GetData() const
	{
		return *pData;
	}
    
	Test& operator=(const Test& rhs) {
		*pData = *rhs.pData;
		return *this;
	}

private:
	int* pData;
};

void TestFunc(const Test& test)
{
	cout << test.GetData() << endl;
}
	
int main() {

	TestFunc(5);
	return 0;
}

위 코드에서보면 TestFunc라는 함수의 매개변수는 Test 타입을 받는다.

 

그런데 main문에서 그냥 int 를 받았는데도 멀쩡하게 동작한다.

출력해보면 변환 생성자가 호출되었음을 알 수 있다.

 

변환 생성자의 매개변수와 같은 타입의 데이터를 전달하였더니 컴파일러가 자동으로 형변환 시켜버린 것이다.

 

이것을 막는 방법은 변환 생성자 앞에 explicit 키워드를 붙이는 것이다.

 

아래와 같이 변환 생성자 앞에 explicit 키워드를 사용하면 자동 형변환이 일어나지 않으며,

만약 이전과 같이 TestFunc에 인자로 상수를 전달할 경우 컴파일 에러가 발생한다.

class Test {
public:
	Test() 
	{
		cout << "Test()" << endl;
		pData = new int(0);
	}

	explicit Test(int _data)//변환 생성자
	{
		cout << "Test(int)" << endl;
		//pData = new int(_data);
		pData = new int(_data);
	}

	Test(const Test& rhs) // 복사생성자 ( Copy COnstructor )
	{
		cout << "CTest(const Test &)" << endl;
		pData = new int(*rhs.pData);
	}
	~Test()
	{
		cout << "~CTest()" << endl;
		delete pData;
	}

	int GetData() const
	{
		return *pData;
	}
	Test& operator=(const Test& rhs) {
		*pData = *rhs.pData;
		return *this;
	}

private:
	int* pData;
};

void TestFunc(const Test& test)
{
	cout << test.GetData() << endl;
}
	
int main() {

	TestFunc(Test(5));
	return 0;
}

결론 : 변환 생성자는 안쓰는 것이 좋으나 써야 한다면 반드시 explicit 키워드를 붙여서 사용하자.

 


허용되는 형변환


허용된다고 해서 꼭 좋은 것은 아니다.

 

class Test {
public:
	Test() 
	{
		cout << "Test()" << endl;
		pData = new int(0);
	}

	explicit Test(int _data)//변환 생성자
	{
		cout << "Test(int)" << endl;
		//pData = new int(_data);
		pData = new int(_data);
	}

	Test(const Test& rhs) // 복사생성자 ( Copy COnstructor )
	{
		cout << "CTest(const Test &)" << endl;
		pData = new int(*rhs.pData);
	}
	~Test()
	{
		cout << "~CTest()" << endl;
		delete pData;
	}

	int GetData() const
	{
		return *pData;
	}
	Test& operator=(const Test& rhs) {
		*pData = *rhs.pData;
		return *this;
	}
	operator int(void) {
		return *pData;
	}
private:
	int* pData;
};

void TestFunc(const Test& test)
{
	cout << test.GetData() << endl;
}
	
int main() {

	Test a(5);
	cout << a << endl;
	return 0;
}

위와 같이 형 변환 연산자를 정의하게 되면 main에서 Test a가 마치 인트처럼 동작을 하게 된다.

 

이유는 변환 생성자의 매개변수 int와 형 변환 int 연산자의 타입이 동일하기 때문이다.

 

이런 경우 class Test는 int와 상당히 높은 호환성을 갖게 된다.

 

Test가 int인지 헷갈릴 정도이다.

 

이것을 막기 위해서 형 변환 연산자에도 explicit 키워드를 붙여주어야 한다.

class Test {
public:
	Test() 
	{
		cout << "Test()" << endl;
		pData = new int(0);
	}

	explicit Test(int _data)//변환 생성자
	{
		cout << "Test(int)" << endl;
		//pData = new int(_data);
		pData = new int(_data);
	}

	Test(const Test& rhs) // 복사생성자 ( Copy COnstructor )
	{
		cout << "CTest(const Test &)" << endl;
		pData = new int(*rhs.pData);
	}
	~Test()
	{
		cout << "~CTest()" << endl;
		delete pData;
	}

	int GetData() const
	{
		return *pData;
	}
	Test& operator=(const Test& rhs) {
		*pData = *rhs.pData;
		return *this;
	}
	explicit operator int(void) {
		return *pData;
	}
private:
	int* pData;
};

void TestFunc(const Test& test)
{
	cout << test.GetData() << endl;
}
	
int main() {

	Test a(5);
	cout << (int)a << endl;
	return 0;
}

형변환 연산자에 explicit 키워드를 추가해주었기 때문에

 

컴파일러가 묵시적으로 형 변환하는 것을 막을 수 있다.