묵시적 변환 - 변환 생성자(Conversion Constructor)

2024. 3. 25. 14:02프로그래밍/C++

#include <iostream>
using namespace std;

class CTestData
{
public:
    //매개변수가 하나뿐인 생성자는 형변환이 가능하다.
    CTestData(int nParam) : m_nData(nParam)
    {
        cout<<"CTestData(int)"<<endl;
    }

    CTestData(const CTestData &rhs) : m_nData(rhs.m_nData)
    {
        cout<<"CTestData(const CTestData &)"<<endl;
    }

    int GetData() const {return m_nData;}
    void SetData(int nParam) {m_nData = nParam;}

private:
    int m_nData=0;
};

//사용자코드
//매개변수가 클래스 형식이며 변환 생성이 가능하다.
void TestFunc(CTestData param)
{
    cout<<"TestFunc(): "<<param.GetData()<<endl;
}

int main()
{
    //int 자료형에서 CTestData 형식으로 변환될 수 있다.
    TestFunc(5);

    return 0;
}

 

'매개변수가 한 개인 생성자' = 변환 생성자 (Conversion constructor)

문제점: 은근슬쩍 제공, 불필요한 임시 객체를 만들어냄으로써 프로그램 효율을 깍아먹는다.

 

위 코드에서 CTestData 클래스는 int 자료형에 대한 변환 생성자를 제공합니다.

CTestData와 int는 전혀 다른 형식이기 때문에 본래 매개변수의 형식이 일치하지 않는다는 오류가 발생해야 합니다. CTestData가 int 자료형에 대한 변환 생성자(CTestData(int))를 제공한 덕분에 TestFunc() 하뭇의 매개변수인 param은 param(5)의 형태로 생성됩니다.

param을 참조 형식으로 변경할 경우,

//사용자코드
//매개변수가 클래스에 대한 참조 형식이며 묵시적으로 변환 생성한다.
void TestFunc(const CTestData &param)
{
    cout<<"TestFunc(): "<<param.GetData()<<endl;
}

int main()
{
    //새로운 CTestData 객체를 생성하고 참조로 전달한다.
    TestFunc(5);

    return 0;
}

변경된 코드에서는 TestFunc 함수의 매개변수가 참조 형식으로 변경되었습니다. 이것은 객체를 복사하지 않고 직접 참조를 전달함을 의미합니다

5는 int 타입이고, CTestData 클래스의 생성자는 int 매개변수를 받는 생성자가 정의되어 있지만, 그것이 명시적 형변환이 아니라 암묵적 형변환을 통한 호출이므로 오류가 발생합니다.

다만 컴파일러가 '알아서' 임시 객체를 생성한 후 임시 캑체에 대한 참조가 TestFunc() 함수로 전달되록 했기 때문에 TestFunc(CTestData(5));로 코딩한 것과 같아진다.

 

클래스 형식을 매개변수로 사용할 경우 확인

  • 참조 형식 사용
  • 묵시적 변환 생성자를 지원하는 클래스인지 확인

묵시적 변환 생성자가 사용자 모르게 호출될 가능성을 차단할 때는 explicit 예약어를 추가한다.

class CTestData
{
public:
    explicit CTestData(int nParam) : m_nData(nParam)
    {
        cout<<"CTestData(int)"<<endl;
    }

 ...
int main()
{
    //int 자료형에서 CTestData 형식으로 변환될 수 있다.
    TestFunc(5);

    return 0;
}

 

 

클래스가 변환 생성자를 제공하면 두 형식 사이에 호환성이 생기지만 이는 반쪽자리 변환이다.

CTest 형식은 int 자료형으로 변환될 수 없기 때문이다.

하지만 형변환 연산자를 만들어 넣으면 불가능한 변환이 가능해진다.

#include <iostream>
using namespace std;

class CTestData
{
public:
    explicit CTestData(int nParam) : m_nData(nParam) { }

    //CTestData 클래스는 int 자료형으로 변환될 수 있다
    operator int(void) {return m_nData;}

    int GetData() const {return m_nData;}
    void SetData(int nParam) {m_nData = nParam;}

private:
    int m_nData = 0;
};

int main()
{
    CTestData a(10);
    cout<< a.GetData() << endl; //10출력

    cout<< a << endl; //10출력
    cout<< (int)a <<endl; //10출력
    cout<< static_cast<int>(a) <<endl; //10출력

    return 0;
}

형변환 연산자 덕분에 int 자료형으로 변환되어 인식될 수 있었다.

(int) a 강제 형변환 연산자라면

static_cast<int>(a)는 '형변환해도 되는 것들'로 제한한 것들만 형변환 시킨다.

  • const_cast
  • static_cast
  • dynamic_cast
  • reinterpret_cast

그렇다면 허용되는 변환을 적용해보자

1. 정수형에서 부동 소수점 형으로 형변환

int a = 10;
double b = static_cast<double>(a);

2. 포인터 간의 형변환 (단, 포인터 타입 사이에 상속 관계가 있거나 void 포인터를 다른 포인터 타입으로 변환 제외)

int* ptrInt;
void* ptrVoid = &a;
ptrInt = sstatic_cast<int*>(ptrVoid);

3. 포인터에서 정수로의 형변환

int* ptrInt = &a;
intptr_t intPtrValue = static_cast<intptr_t>(ptrInt);

4. 열거형에서 정수형으로 형변환

enum class Color {RED, GREEN, BLUE };
int colorValue = static_cast<int>(Color::RED);

5. 상수의 형변환

const int& refConstInt = 10;
int nonConstInt = static_cast<int>(refConstInt);

'프로그래밍 > C++' 카테고리의 다른 글

상속이란  (1) 2024.03.27
임시 객체와 이동 시맨틱  (1) 2024.03.26
대입 vs 복사  (0) 2024.03.22
Pass by value VS Pass by reference  (0) 2024.03.21
대입 연산자  (0) 2024.03.21