[C++23 새기능 소개] std::basic_string::resize_and_overwrite()

C++23에서는 std::basic_string 타입에 더욱 세밀한 제어와 성능 최적화를 제공하기 위해 새로운 멤버 함수인 resize_and_overwrite()가 추가되었습니다. 이번 글에서는 std::basic_string::resize_and_overwrite()의 개념과 사용법, 그리고 이전 버전과 비교하여 어떠한 개선점을 제공하는지 알아보겠습니다.

resize_and_overwrite()란 무엇인가요?

C++20까지의 std::basic_string은 문자열을 다룰 때 주로 resize(), reserve(), append() 등의 멤버 함수를 사용했습니다. 하지만 이들 함수는 문자열의 크기 조정이나 추가 작업을 수행할 때 여러 번의 복사나 메모리 이동이 발생할 수 있었고, 문자열의 내부 버퍼에 직접적으로 접근하기 위한 안전한 표준화된 메커니즘이 부족했습니다.

C++23에서 도입된 resize_and_overwrite() 멤버 함수는 다음과 같은 특징을 갖습니다:

  • 문자열 크기를 재조정한 뒤, 콜백 함수를 통해 직접 내부 버퍼를 수정할 수 있습니다.
  • 콜백 함수는 재조정된 버퍼 영역에 대해 쓰기 연산을 수행하고, 최종적으로 새로운 문자열의 길이를 반환합니다.
  • 이를 통해 여러 번의 복사 없이 한 번에 문자열 내용을 갱신할 수 있으며, 성능 최적화와 세밀한 제어를 가능하게 합니다.

이전 버전에서는 어떻게 했나요?

C++20까지 문자열을 편집할 때는 주로 다음과 같이 했습니다.

예제: 기존 방식 (C++20까지)

#include <string>
#include <algorithm>
#include <iostream>

int main() {
    std::string str = "Hello";
    str.resize(str.size() + 5);  // 공간 확보
    // resize 이후 str의 끝 부분을 직접 다루려면?
    // 일반적으로는 insert, append 등을 사용하거나
    // &str[0]를 통해 비표준적이게 내부 버퍼를 참조
    
    // 예: 대문자로 변환 (단, 여기서는 단순 예시)
    std::transform(str.begin(), str.end(), str.begin(), ::toupper);

    std::cout << str << '\n'; 
    return 0;
}
  • 문제점:
    • resize 후 내부 버퍼에 직접 접근은 비표준적이거나 위험할 수 있음.
    • 문자열 수정 과정에서 중간에 여러 번 복사나 추가 작업이 수행될 수 있음.
    • 최종 문자열 길이를 설정하는 과정이 번거로울 수 있음.

C++23의 resize_and_overwrite()를 사용한 개선

resize_and_overwrite()는 콜백 함수를 인자로 받아, 다음과 같이 동작합니다:

  1. 문자열 크기를 지정한 크기로 재조정.
  2. 콜백 함수에 내부 버퍼의 포인터와 새로 확보된 공간의 길이를 전달.
  3. 콜백 함수 내에서 그 공간을 직접 수정한 뒤, 최종적으로 실제 사용된 문자의 수(널 종단 문자 제외)를 반환.
  4. resize_and_overwrite()는 반환값을 바탕으로 문자열의 실제 크기를 설정.

예제: resize_and_overwrite() 사용

#include <string>
#include <iostream>
#include <cctype>

int main() {
    std::string str = "Hello";
    // 문자열을 대문자로 바꾸면서 길이를 10으로 맞추고, 
    // 뒤쪽에 '!' 4개를 추가한다고 가정
    
    str.resize_and_overwrite(10, [](char* data, std::size_t size) {
        // 현재 data에는 길이 10의 버퍼가 있고,
        // 여기서 원래 "Hello" 내용과 추가 공간이 있음.
        
        // 원래 "Hello" (길이 5)를 대문자로 변환
        for (std::size_t i = 0; i < 5; ++i) {
            data[i] = static_cast<char>(std::toupper(static_cast<unsigned char>(data[i])));
        }

        // 뒤에 "!!!!" 추가
        std::size_t idx = 5;
        for (int j = 0; j < 4; ++j) {
            data[idx++] = '!';
        }

        // 최종적으로 사용한 문자 수: 9 (H E L L O ! ! ! !)
        // 하지만 여기서 주의: 실제로 널 종단은 자동 처리되므로 9개 문자를 사용했음.
        // 이때 반환값은 실제로 채워진 문자 수
        return static_cast<std::size_t>(9);
    });

    std::cout << str << '\n'; // "HELLO!!!!"
    // 최종 길이는 9. (10으로 resize 했지만, callback에서 9를 반환했으므로)
    return 0;
}
  • 이 예제에서 resize_and_overwrite()는 한 번의 콜백 호출로 내부 버퍼를 직접 조작하고, 최종 길이를 설정하여 불필요한 중간 작업을 줄입니다.
  • 기존 방식에 비해 코드가 명확하고, 성능 상 이점도 기대할 수 있습니다.

어떻게 좋아졌나요?

  • 성능 최적화: 복사나 insert와 같은 중간 작업 없이 한 번에 내부 버퍼를 갱신 가능.
  • 명확한 버퍼 제어: 콜백 내에서 버퍼를 직접 다루므로, 로직이 명확해지고 중간 불필요한 스텝 감소.
  • 코드 가독성 향상: resize 후 별도 조작 없이 한 함수로 최종 문자열 상태를 정의.
  • 정확한 길이 설정: 반환값을 통해 최종 문자열 길이를 정확히 설정 가능.

상세한 예제와 비교

기존 방식 vs resize_and_overwrite()

기존 방식(C++20 이전)

str.resize(new_size);
// resize 후 for문이나 알고리즘으로 일부 변경
// 필요하다면 str.erase()나 append()로 길이 재조정

C++23 방식

str.resize_and_overwrite(new_size, [](char* data, std::size_t size) {
    // data를 자유롭게 조작
    // 사용한 문자 수 반환
});
  • 한 번의 호출로 크기 조정, 내용 변경, 최종 길이 설정까지 처리 가능.

주의 사항

  • 콜백 함수 내 안정성: 콜백 내에서 data 포인터를 조작할 때, bounds를 초과하지 않도록 주의.
  • 인코딩 문제: 문자열이 UTF-8 등 멀티바이트 인코딩이면 단순 char 단위 조작 시 주의 필요.
  • 컴파일러 지원: C++23 기능이므로, 이를 지원하는 컴파일러 및 표준 라이브러리가 필요.

요약

C++23에서 추가된 std::basic_string::resize_and_overwrite()는 문자열 내부 버퍼를 직접 조작할 수 있는 강력한 도구를 제공합니다. 이를 통해 불필요한 중간 연산을 줄이고, 한 번의 호출로 크기 조정, 내용 변경, 최종 길이 설정을 수행할 수 있어 코드 가독성과 성능 면에서 이점을 얻을 수 있습니다. 문자열 처리 로직이 복잡하거나 성능에 민감한 영역에서 특히 유용하게 활용될 수 있습니다.

 

참고 자료:

반응형