C++ 타입 변환 | "Type Conversion" 가이드

C++ 타입 변환 | "Type Conversion" 가이드

이 글의 핵심

C++ 타입 변환에 대해 정리한 개발 블로그 글입니다. int x = 10; double y = x;

암시적 변환

컴파일러가 자동으로 수행하는 변환

int x = 10;
double y = x;  // int -> double (암시적)

float f = 3.14;
int z = f;  // float -> int (암시적, 소수점 버림)

bool b = 42;  // int -> bool (0이 아니면 true)

명시적 변환 (캐스팅)

double d = 3.14;

// C 스타일 캐스트
int x = (int)d;

// C++ 스타일 캐스트
int y = static_cast<int>(d);

변환 생성자

class Distance {
private:
    double meters;
    
public:
    // 변환 생성자
    Distance(double m) : meters(m) {}
    
    double getMeters() const {
        return meters;
    }
};

void printDistance(Distance d) {
    cout << d.getMeters() << "m" << endl;
}

int main() {
    printDistance(100.5);  // double -> Distance (암시적)
    printDistance(Distance(50.0));  // 명시적
}

explicit 키워드

class Distance {
private:
    double meters;
    
public:
    // explicit: 암시적 변환 금지
    explicit Distance(double m) : meters(m) {}
    
    double getMeters() const {
        return meters;
    }
};

void printDistance(Distance d) {
    cout << d.getMeters() << "m" << endl;
}

int main() {
    // printDistance(100.5);  // 에러: 암시적 변환 불가
    printDistance(Distance(100.5));  // OK: 명시적
}

변환 연산자

class Distance {
private:
    double meters;
    
public:
    Distance(double m) : meters(m) {}
    
    // 변환 연산자: Distance -> double
    operator double() const {
        return meters;
    }
};

int main() {
    Distance d(100.5);
    double x = d;  // Distance -> double (암시적)
    
    cout << x << endl;  // 100.5
}

실전 예시

예시 1: 문자열 래퍼

class String {
private:
    string data;
    
public:
    String(const char* str) : data(str) {}
    
    explicit String(const string& str) : data(str) {}
    
    // string으로 변환
    operator string() const {
        return data;
    }
    
    // const char*로 변환
    operator const char*() const {
        return data.c_str();
    }
    
    size_t length() const {
        return data.length();
    }
};

int main() {
    String s("Hello");
    
    string str = s;  // String -> string
    const char* cstr = s;  // String -> const char*
    
    cout << str << endl;
    cout << cstr << endl;
}

예시 2: 스마트 포인터

template<typename T>
class SmartPtr {
private:
    T* ptr;
    
public:
    explicit SmartPtr(T* p = nullptr) : ptr(p) {}
    
    ~SmartPtr() {
        delete ptr;
    }
    
    // bool로 변환 (nullptr 체크)
    explicit operator bool() const {
        return ptr != nullptr;
    }
    
    T& operator*() const {
        return *ptr;
    }
    
    T* operator->() const {
        return ptr;
    }
};

int main() {
    SmartPtr<int> p1(new int(42));
    SmartPtr<int> p2;
    
    if (p1) {  // bool로 변환
        cout << *p1 << endl;
    }
    
    if (!p2) {
        cout << "p2는 nullptr" << endl;
    }
}

예시 3: 온도 클래스

class Celsius {
private:
    double temp;
    
public:
    explicit Celsius(double t) : temp(t) {}
    
    double get() const {
        return temp;
    }
};

class Fahrenheit {
private:
    double temp;
    
public:
    explicit Fahrenheit(double t) : temp(t) {}
    
    // Celsius로 변환
    Fahrenheit(const Celsius& c) 
        : temp(c.get() * 9.0 / 5.0 + 32.0) {}
    
    double get() const {
        return temp;
    }
};

int main() {
    Celsius c(100.0);
    Fahrenheit f = Fahrenheit(c);  // Celsius -> Fahrenheit
    
    cout << "섭씨 " << c.get() << "도" << endl;
    cout << "화씨 " << f.get() << "도" << endl;
}

예시 4: 분수 클래스

class Fraction {
private:
    int numerator;
    int denominator;
    
public:
    Fraction(int n, int d = 1) 
        : numerator(n), denominator(d) {}
    
    // double로 변환
    explicit operator double() const {
        return static_cast<double>(numerator) / denominator;
    }
    
    // int로 변환 (정수 부분만)
    explicit operator int() const {
        return numerator / denominator;
    }
    
    void print() const {
        cout << numerator << "/" << denominator;
    }
};

int main() {
    Fraction f(3, 4);
    
    double d = static_cast<double>(f);  // 0.75
    int i = static_cast<int>(f);        // 0
    
    cout << "분수: ";
    f.print();
    cout << endl;
    cout << "실수: " << d << endl;
    cout << "정수: " << i << endl;
}

표준 변환 순서

// 1. 정수 승격
char c = 'A';
int x = c;  // char -> int

// 2. 정수 변환
long l = 100;
int y = l;  // long -> int

// 3. 부동소수점 변환
float f = 3.14f;
double d = f;  // float -> double

// 4. 포인터 변환
int* p = nullptr;  // nullptr -> int*

// 5. bool 변환
bool b = 42;  // int -> bool

사용자 정의 변환 규칙

class A {
public:
    A(int x) {}  // int -> A
};

class B {
public:
    B(const A& a) {}  // A -> B
};

int main() {
    // int -> A -> B (최대 1번의 사용자 정의 변환)
    // B b = 42;  // 에러: 2번의 변환 필요
    
    A a = 42;  // OK: int -> A
    B b = a;   // OK: A -> B
}

자주 발생하는 문제

문제 1: 의도하지 않은 변환

class Array {
private:
    int* data;
    size_t size;
    
public:
    // ❌ 암시적 변환 허용
    Array(size_t s) : size(s), data(new int[s]) {}
    
    ~Array() {
        delete[] data;
    }
};

void func(Array arr) {}

int main() {
    func(10);  // 의도하지 않은 변환
}

// ✅ explicit 사용
class Array {
public:
    explicit Array(size_t s) : size(s), data(new int[s]) {}
    // ...
};

// func(10);  // 에러
func(Array(10));  // OK

문제 2: 변환 연산자 남용

class Number {
private:
    int value;
    
public:
    Number(int v) : value(v) {}
    
    // ❌ 암시적 변환 (혼란 야기)
    operator int() const {
        return value;
    }
};

Number n(42);
int x = n + 10;  // 혼란스러움

// ✅ explicit 사용
class Number {
public:
    explicit operator int() const {
        return value;
    }
};

int x = static_cast<int>(n) + 10;  // 명확

문제 3: 변환 체인

class A {
public:
    A(int x) {}
};

class B {
public:
    B(const A& a) {}
};

// ❌ 2번의 변환 (불가)
// B b = 42;  // int -> A -> B

// ✅ 명시적 변환
A a = 42;
B b = a;

// 또는
B b = B(A(42));

explicit(bool) (C++20)

template<typename T>
class Optional {
private:
    T value;
    bool hasValue;
    
public:
    Optional() : hasValue(false) {}
    Optional(const T& v) : value(v), hasValue(true) {}
    
    // 조건부 explicit
    template<typename U>
    explicit(!is_convertible_v<U, T>)
    Optional(const U& v) : value(v), hasValue(true) {}
    
    explicit operator bool() const {
        return hasValue;
    }
};

FAQ

Q1: explicit은 언제 사용?

A:

  • 단일 인자 생성자
  • 변환 연산자
  • 의도하지 않은 변환 방지

Q2: 변환 생성자 vs 변환 연산자?

A:

  • 변환 생성자: 다른 타입 → 내 타입
  • 변환 연산자: 내 타입 → 다른 타입

Q3: C 스타일 vs C++ 스타일 캐스트?

A: C++ 스타일 권장 (명확, 안전).

Q4: 암시적 변환은 나쁜가?

A: 편리하지만 버그 유발 가능. explicit으로 제어.

Q5: 변환 체인은?

A: 최대 1번의 사용자 정의 변환만 허용.

Q6: 타입 변환 학습 리소스는?

A:

  • “Effective C++”
  • cppreference.com
  • “C++ Primer”

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

관련 글

  • C++ Copy Initialization |