간단하게 매개변수가 한 개인 생성자를 '변환 생성자'라고 합니다. 이러한 '변환 생성자'는 '사용자 코드'에서의 편의를 위해 묵시적으로 호출될 수 있습니다. 예제를 살펴보겠습니다.

class MyData {
private:
    int m_nData = 0;
    
public:
    MyData(int nParam) : m_nData{ nParam } { cout << "MyData(int)" << endl; }
    
    MyData(const MyData& rhs) : m_nData{ rhs.m_nData } {
        cout << "MyData(const MyData&)" << endl;
    }
    
    int GetData() const { return m_nData; }
    
    void SetData(int nParam) { m_nData = nParam; }
}

void Func(MyData param) {
    cout << "Func(): " << param.GetData() << endl;
}

void main() {
    Func(10); // Func() 함수의 매개변수는 'MyData' 타입이지만, int 값을 넘겨줍니다.
              // 그럼 Func() 함수는 int 매개변수 하나를 가지는 '생성자'를 찾아 호출합니다.
}

 

위의 예제는 컴파일 에러가 나지않고 잘 실행됩니다. Func() 함수는 int형 매개변수가 아니라, MyData 사용자 정의 타입인데 왜 잘 실행되는걸까요? 그 이유가 바로 위에서 설명했듯이 '사용자 코드'에서의 편의를 위해 묵시적으로 '변환 생성자'를 호출했기 때문입니다. 위의 코드에서 Func(10);은 사실상 아래 코드와 같습니다.

Func(MyData{ 10 });

 

Func() 함수의 매개변수를 '&'로 받으면 이 이름없는 '임시 객체'를 '참조'할 수 있게 됩니다. 위와 똑같은 코드에서 Func() 함수의 매개변수만 '&'로 수정해보겠습니다.

void Func(const MyData& param) {
    cout << "Func(): " << param.GetData() << endl;
}

void main() {
    Func(10); // Func(MyData{ 10 });
}

 

'변환 생성자'는 '사용자 코드'에서의 편의를 위해 제공되지만, 제공받는 편의에 비해 치뤄야할 비용이 더 큰 생성자입니다. '사용자'가 의도하지 않았음에도 불구하고 묵시적으로 호출되어 '임시 객체'가 생성되는 문제가 발생할 수 있습니다. 묵시적으로 '변환 생성자'가 호출되지 않도록 막기 위해선 'explicit' 키워드를 사용합니다.

class MyData {
private:
    int m_nData = 0;
    
public:
    explicit MyData(int nParam) : m_nData{ nParam } { cout << "MyData(int)" << endl; }
    
   /* 아래 코드는 위와 동일합니다. */
}

 

자, 여기서 추가로 생각해봐야할 문제가 있습니다. 우리가 지금껏 살펴봤던 변환은 'int -> MyData' 타입으로의 변환이었습니다. 그러면 반대로 'MyData -> int' 타입으로의 변환도 가능할까요? 물론 가능합니다. MyData 클래스에 '형변환 연산자'를 추가해보겠습니다.

class MyData {
private:
    int m_nData = 0;
    
public:
    explicit MyData(int nParam) : m_nData{ nParam } { cout << "MyData(int)" << endl; }
    
    operator int(void) { return m_nData; } // 해당 클래스는 'int'형으로 형변환이 가능해집니다.
    
    /* 아래 코드는 위와 동일합니다. */
}

 

마지막으로, 'MyData -> int'형으로 형변환을 할 때는 C++ 스타일의 'static_cast<int>(형변환 연산자)'를 사용합니다.

cout << static_cast<int>(a) << endl; // '(int)a'는 지양합니다.