나만의 작은 도서관

[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;
}