변환 연산자 오버로딩, 변환 생성자

Jaemyeong Lee·2024년 8월 20일
0

FastCampusC++

목록 보기
66/78

C++ 변환 연산자 오버로딩 및 변환 생성자 분석 및 정리

이번 포스트에서는 C++에서 변환 연산자 오버로딩과 변환 생성자의 개념을 살펴보겠습니다. 특히 explicit 키워드를 사용할 때와 사용하지 않을 때의 차이점을 중심으로 분석해보겠습니다.

코드 분석

#pragma warning(disable: 4996)
#include <iostream>

using namespace std;
  • #pragma warning(disable: 4996): 이 지시어는 컴파일러가 "안전하지 않은" C 표준 라이브러리 함수들(strcpy, strcat 등)에 대해 경고하는 것을 비활성화합니다.
  • #include <iostream>: 입출력 스트림 라이브러리를 포함합니다.
  • using namespace std;: 표준 라이브러리의 네임스페이스를 전역적으로 사용하도록 합니다.
class String
{
private:
    char* _chars;
  • 클래스 String의 멤버 변수:
    • _chars: char* 타입으로 문자열을 저장합니다. 동적 메모리 할당을 통해 관리됩니다.
public:
    /* explicit */ String(const char* chars) // explicit 를 사용하지 않으면 암시적으로 형변환이 된다.
        : _chars(new char[strlen(chars) + 1])
    {
        strcpy(_chars, chars);
    }
  • 변환 생성자:
    • String(const char* chars): 이 생성자는 const char* 타입의 문자열을 String 객체로 변환하는 생성자입니다. 이 생성자가 explicit 키워드 없이 선언되면, const char* 타입의 문자열을 String 타입으로 암시적으로 변환할 수 있습니다.
    • 동작: 문자열의 길이만큼 메모리를 할당한 후, strcpy로 문자열을 복사합니다.
    • explicit: 이 키워드를 주석에서 제거하면, 암시적 변환이 발생하지 않게 됩니다. 즉, String 타입의 객체로 변환하려면 명시적인 변환을 해야 합니다.
    /* explicit */ String(const char* s0, const char* s1)
        : _chars(new char[strlen(s0) + strlen(s1) + 1])
    {
        _chars[0] = '\0';
        strcat(_chars, s0);
        strcat(_chars, s1);
    }
  • 다중 인자 변환 생성자:
    • String(const char* s0, const char* s1): 두 개의 const char* 타입의 문자열을 받아서 하나의 String 객체로 결합하는 생성자입니다.
    • 동작: 두 문자열의 길이를 합산하여 메모리를 할당하고, 첫 번째 문자열을 복사한 후, 두 번째 문자열을 이어 붙입니다.
    • explicit: 이 키워드를 주석에서 제거하면, 두 개의 const char* 문자열을 자동으로 결합하여 String 객체로 변환하지 않게 됩니다.
    /* explicit */ String(std::initializer_list<const char*> strs)
        : _chars(nullptr)
    {
        size_t size = 0;
        for (const char* str : strs)
        {
            size += strlen(str);
        }
        _chars = new char[size + 1];
        _chars[0] = '\0';

        for (const char* str : strs)
        {
            strcat(_chars, str);
        }
    }
  • std::initializer_list를 사용하는 변환 생성자:
    • String(std::initializer_list<const char*> strs): 여러 개의 const char* 문자열을 initializer_list 형태로 받아서 하나의 String 객체로 결합하는 생성자입니다.
    • 동작: 리스트에 포함된 모든 문자열의 길이를 합산하여 메모리를 할당한 후, 각 문자열을 순차적으로 복사하여 결합합니다.
    • explicit: 이 키워드를 주석에서 제거하면, 여러 개의 const char* 문자열 리스트가 암시적으로 String 객체로 변환되지 않게 됩니다.
    ~String()
    {
        delete[] _chars;
    }
  • 소멸자:
    • 동적으로 할당된 메모리를 해제합니다. 객체가 파괴될 때, 메모리 누수를 방지합니다.
    // 논리 연산자 오버로딩 대신 bool 변환 연산자를 고려해보자
    /* explicit */ operator bool() const // explicit 가 없다면 암시적으로도 형변환이 가능해진다
    {
        return strlen(_chars) > 0;
    }
  • 변환 연산자:
    • operator bool(): 이 연산자는 String 객체를 bool 타입으로 변환할 수 있게 해줍니다. _chars 문자열의 길이가 0보다 크면 true를 반환하고, 그렇지 않으면 false를 반환합니다.
    • explicit: 이 키워드를 주석에서 제거하면, String 객체가 bool 타입으로 암시적으로 변환되지 않게 됩니다. 따라서, 조건문에서의 사용 시 명시적인 변환이 필요합니다.
    void print() const
    {
        cout << _chars << endl;
    }
};
  • 출력 함수:
    • print(): 객체의 문자열을 출력합니다.

main 함수 설명

void func(String s)
{
    s.print();
}
  • func 함수:
    • 이 함수는 String 객체를 매개변수로 받아서 해당 문자열을 출력합니다.
int main()
{
    String s0 = "abc"; // 암시적 변환
    s0.print();
  • 암시적 변환:
    • String s0 = "abc";는 암시적으로 const char* 타입의 문자열 "abc"String 객체로 변환합니다. 이때 String(const char*) 생성자가 호출됩니다.
    func("abc"); // 암시적 변환
  • 함수 호출 시 암시적 변환:
    • func("abc");는 암시적으로 "abc"String 객체로 변환하여 func 함수에 전달합니다. 이때도 String(const char*) 생성자가 호출됩니다.
    String s1{ "abc", "efg" };
    s1.print();
  • 다중 인자 생성자를 통한 객체 생성:
    • String s1{ "abc", "efg" };는 두 개의 문자열을 결합하여 String 객체를 생성합니다. 이때 String(const char*, const char*) 생성자가 호출됩니다.
    String s2{ "a", "b", "c", "d" };
    s2.print();
  • initializer_list를 통한 객체 생성:
    • String s2{ "a", "b", "c", "d" };는 네 개의 문자열을 결합하여 String 객체를 생성합니다. 이때 String(std::initializer_list<const char*>) 생성자가 호출됩니다.
    String s3 = "a";
    bool result = s3; // 변환 연산자
    cout << result << endl;
  • 암시적 변환 연산자:
    • bool result = s3;String 객체 s3bool 타입으로 암시적으로 변환합니다. 이때 operator bool()이 호출되어 문자열이 비어 있지 않으므로 true가 출력됩니다.
    if (s3) // 변환 연산자
    {
        s3.print();
    }
  • 조건문에서의 변환 연산자:
    • if (s3)s3 객체를 bool로 변환하여 조건을 평가합니다. operator bool()이 호출되어 s3의 문자열이 비어 있지 않으므로 true가 되어 s3.print();가 실행됩니다.
    String s4 = "";
    result = s4;
    cout << result << endl;
  • 빈 문자열의 변환 연산자:
    • String s4 = "";로 빈 문자열 객체를 생성하고, result = s4;에서 s4 객체를 bool로 변환합니다. 이때 operator bool()이 호출되어 빈 문자열이므로 false가 출력됩니다.
    if (s4)
    {
        s4.print();
    }
  • 조건문에서 빈 문자열의 변환 연산자:
    • if (s4)s4 객체를 bool로 변환하여 조건을 평가합니다. 이때 s4가 빈 문자열이므로 false가 되어 s4.print();는 실행되지 않습니다.

explicit

키워드의 차이점

  • explicit 사용 시:

    • 암시적 변환 차단: explicit 키워드를 사용하면, 생성자와 변환 연산자에서 암시적 형 변환이 발생하지 않습니다. 즉, String s = "abc"; 같은 코드에서 컴파일 오류가 발생합니다. 명시적으로 String s("abc"); 또는 String s = String("abc");와 같이 사용해야 합니다.
    • 코드의 명시성 증가: 암시적 변환이 발생하지 않으므로, 코드의 가독성과 안정성이 증가합니다. 의도하지 않은 변환으로 인한 오류를 방지할 수 있습니다.
  • explicit 미사용 시:

    • 암시적 변환 허용: explicit 키워드를 사용하지 않으면, const char* 타입의 문자열이 자동으로 String 객체로 변환됩니다. 이는 편리할 수 있으나, 의도하지 않은 변환이 발생할 위험이 있습니다.
    • 유연성 증가: 코드의 유연성이 증가하지만, 명확성이 떨어지고 디버깅 시 오류를 추적하기 어려울 수 있습니다.

결론

이번 포스트에서는 C++에서 변환 연산자 오버로딩과 변환 생성자에 대해 학습했습니다. explicit 키워드를 사용하는 것과 사용하지 않는 것의 차이를 이해하고, 코드의 안정성과 가독성을 고려하여 적절히 활용하는 것이 중요합니다. 이번 예제를 통해 객체의 암시적 변환을 제어하고 명시적인 코드를 작성하는 방법을 익혀보세요!

profile
李家네_공부방

0개의 댓글