나만의 작은 도서관
[TIL][C++] 250807 MMO 서버 개발 75일차: C++ 문자열에 대한 정리(’L’ 접두사, wstring, wchar, TCHAR), C++ throw - catch 사용하는 법 본문
Today I Learn
[TIL][C++] 250807 MMO 서버 개발 75일차: C++ 문자열에 대한 정리(’L’ 접두사, wstring, wchar, TCHAR), C++ throw - catch 사용하는 법
pledge24 2025. 8. 8. 04:59주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
C++ 문자열에 대한 정리(’L’ 접두사, wstring, wchar, TCHAR)
문자열 리터럴 앞에 붙는 ‘L’ 접두사는 정확히 뭘까?
- wstring에 한글 문자열 리터럴을 저장할 때마다 ‘L’ 접두사를 붙이고는 하는데, 이 L 접두사의 정확한 기능이 뭔지 알고 싶어 졌다.
- L 접두사는 wide-character 문자열 리터럴임을 의미하는 접두사로, L 접두사를 붙인 문자열 리터럴은 const wchar_t로 해석되게 된다.
"ABC" // const char[4] 타입. ASCII, 일반 문자열
L"ABC" // const wchar_t[4] 타입. wide character 문자열
그럼 wchar_t는 무엇인가?
- wide-character 문자 타입으로, 각 문자를 표현할 때 1바이트가 아닌 여러 바이트로 표현된다.
- 보통 wchar_t는 Windows 환경에서 UTF-16(2바이트)로 문자를 표현하는 데 사용한다.
- wchar_t는 매크로 이름인 WCHAR를 더 자주 사용하는데, 이유는 별거 없고 보다 가독성이 높기 때문이다.
typedef wchar_t WCHAR; // 이렇게 정의되어 있다.
string vs wstring
- string이든 wstring이든 근본은 basic_string이라는 것. 이 둘의 차이는 그저 템플릿 인자의 차이이다. 즉,
- string = basic_string <char>
- wstring = basic_string <wchar_t>
- 라는 것이다. 표로 정리하면 아래와 같다.
| 항목 | std::string | std::wstring |
| 내부 타입 | char (1바이트) | wchar_t (Windows에선 2바이트) |
| 인코딩 | 보통 UTF-8, ASCII 등 | 보통 UTF-16 (Windows), UCS-4 (Linux) |
| 목적 | 영문 중심의 문자열 처리 | 유니코드 포함 다국어 문자열 처리 |
- 영어가 아닌 다른 문자(ex. 한글)를 string 타입에 넣을 수 있는데, 이런 문자는 UTF-8로 인코딩 되어 넣어진다.
string이 basic_string <char> 임에도 불구하고 한글을 넣을 수 있는 이유 - string은 “문자열”이 아닌 “문자 바이트 배열”
- UTF-8에서 3바이트의 크기를 가지는 한글을 char 타입인 string에 넣을 수 있는 것일까?
- 그것은 바로 문자를 바이트 단위로 쪼개 담기 때문. string은 char 배열로 표현 가능하다면, 어떤 바이트 시퀀스라도 저장할 수 있다.
std::string s = "한" // UTF-8 한글은 3바이트(ANSI로 해석될 수도 있음)
s.size() == 3; // 바이트 배열 기준 크기는 3
// s에 저장되어 있는 형태
// [0xEd][0x95][0x9c]...
- 여기서 주의할 점은 “char 배열로 표현 가능하다면”이라는 부분이다. 문자가 char 타입이라면 상관없지만, char 타입이 아닌 wchar_t라면 string에 직접 넣을 수 없다. 그래서 L 접두사를 붙인 문자열 리터럴의 경우 “타입 불일치”로 인한 컴파일 에러가 발생한다.
std::string s = L"한" // ❌ 오류! 타입 불일치
std::wstring ws = L"한글"; // ✅ wchar_t 기반이므로 정상 동작
Windows 전용 문자 타입 매크로, TCHAR
- TCHAR는 유니코드(UTF-16)와 멀티바이트(ANSI/EUC-KR 등…) 환경을 동시에 지원하기 위해 만들어진 문자 타입이다.
- _T("문자열"), TEXT("문자열") 등을 사용하면 TCHAR에 맞는 문자열 리터럴로 변환된다.
- 하지만, TCHAR는 현대 C++에서 사용을 권장하지 않는다. 대신보다 명시적인 char, wchar_t, char8_t 등을 쓰는 것을 권장한다.
C++ throw - catch 사용하는 법
- try_catch 문을 사용하기는 하지만 수동으로 throw를 해본 적이 없어 조금 헤맸기 때문에 이에 대한 기록을 하고자 한다.
throw를 던지면 그대로 catch문으로 이동한다.
- 만약 코드가 try문에 있고, 해당 try문에서 throw를 했다면 catch문으로 이동한다. 즉, 런타임 에러가 발생해도 크래시가 나지 않고 catch문으로 이동한다는 것이다. 이는 자명하다.
throw는 타입이 맞는 catch문으로만 이동한다.
- try-catch문이라고 해서 try에 발생한 오류가 반드시 catch문으로 이동하는 것은 아니다. throw 된 타입과 catch의 매개변수 타입이 다르면 해당 catch문으로 이동하지 않는다.
try{
throw std::string("error"); // 이동할 catch문이 없어서 런타임 에러 발생
}catch(exception& err){
cout << err << '\\n'
}
exception 클래스
- exception은 표준에서 정의한 예외 클래스를 의미하며, 여러 예외들이 exception을 “상속”받고 있는 형태를 띤다.
- 우리가 마주하는 대부분의 크래시는 exception catch문에 다 걸린다고 보면 된다.
- exception의 하위 클래스로 throw를 하면 exception의 catch 문으로 이동할 수 있다.
try {
throw std::runtime_error("Something went wrong"); // exception의 하위 클래스이므로 catch문 이동 가능
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
- 대표적인 exception 하위 클래스들을 나열하자면 아래와 같다.
논리 오류 예외
| 클래스 이름 | 설명 |
| std::logic_error | 논리 오류의 기본 클래스 |
| std::invalid_argument | 잘못된 인자 전달 |
| std::domain_error | 정의되지 않은 수학 도메인 접근 |
| std::length_error | 컨테이너 크기가 너무 클 때 |
| std::out_of_range | 범위를 벗어난 접근 |
런타임 오류 예외
| 클래스 이름 | 설명 |
| std::runtime_error | 런타임 오류의 기본 클래스 |
| std::range_error | 계산 결과가 범위를 벗어남 |
| std::overflow_error | 산술 연산 중 오버플로우 |
| std::underflow_error | 산술 연산 중 언더플로우 |
상속 구조 요약
std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::domain_error
│ ├── std::length_error
│ └── std::out_of_range
└── std::runtime_error
├── std::range_error
├── std::overflow_error
└── std::underflow_error
catch문은 여러 개 배치할 수 있다.
- try-catch문이니까 1대 1로만 사용할 수 있는 줄 알았는데, 여러 catch문을 하나의 try 문에 붙일 수 있다는 걸 알게 되었다.
- 여러 catch문을 붙이게 되면, 예외가 던져졌을 때 가장 먼저 매칭되는 catch문으로 이동한다.
try {
// 실행 시 1번째로 이동
throw std::runtime_error("Something went wrong");
// 실행 시 2번째로 이동
throw std::wstring(L"오류 발생")
} catch (const std::wstring& errMsg) {
std::wcout << errMsg << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
