이번 포스트에서는 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()
: 객체의 문자열을 출력합니다.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
객체 s3
를 bool
타입으로 암시적으로 변환합니다. 이때 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
키워드를 사용하는 것과 사용하지 않는 것의 차이를 이해하고, 코드의 안정성과 가독성을 고려하여 적절히 활용하는 것이 중요합니다. 이번 예제를 통해 객체의 암시적 변환을 제어하고 명시적인 코드를 작성하는 방법을 익혀보세요!