[C++23 새기능 소개] std::ranges::lazy_split_view

 

C++23에서는 범위(Range) 라이브러리를 한층 풍성하게 하는 새로운 뷰(View) 어댑터가 추가되었습니다. 그중 하나가 바로 std::ranges::lazy_split_view인데, 이 뷰는 기존의 std::ranges::split_view와 유사하지만, '지연(lazy)'이라는 특성을 통해 더욱 효율적으로 문자열이나 시퀀스를 구분할 수 있게 해줍니다. 이번 글에서는 std::ranges::lazy_split_view의 개념과 사용법, 그리고 이전 버전과 비교하여 어떠한 개선점을 제공하는지 알아보겠습니다.

 

std::ranges::lazy_split_view란 무엇인가요?

C++20부터 도입된 범위 라이브러리는 std::ranges::split_view를 통해 입력 범위를 특정 구분 기호(delimiter)를 기준으로 부분 범위(subrange)로 나누어 접근하는 기능을 제공했습니다. 하지만 split_view는 전체 입력 범위를 한 번에 처리해야 하는 상황에서, 큰 입력 데이터나 무거운 처리에 비효율적일 수 있었습니다.

 

C++23의 std::ranges::lazy_split_view는 이러한 문제를 해결하기 위해, 구분 작업을 지연시켜 필요할 때마다 부분 범위를 생성합니다. 즉, 전체 데이터를 한 번에 나누지 않고, 순회 과정에서 요청되는 시점에만 다음 부분 범위를 계산하기 때문에 메모리 사용량과 초기 처리 비용을 절약할 수 있습니다.

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

C++20에서는 이미 std::ranges::split_view를 통해 비슷한 작업을 할 수 있었습니다.

예제: 기존 std::ranges::split_view 방식 (C++20)

#include <ranges>
#include <string_view>
#include <iostream>

int main() {
    std::string_view text = "C++20:Ranges:Split:View";
    auto sv = std::ranges::split_view(text, ':');

    for (auto part : sv) {
        std::cout << std::string_view(part.begin(), part.end()) << '\n';
    }
    return 0;
}
  • split_view는 구분 기호(':')를 기준으로 text를 나누어 부분 범위를 제공합니다.
  • 하지만 이 과정에서 split_view는 내부적으로 전체 구분 작업을 eager(적극적으로) 수행하거나, 최소한 모든 구분점을 한 번은 훑어야 할 수 있습니다.
  • 큰 데이터에 대해 부분 결과를 바로 필요로 할 때 불필요한 선행 작업이 발생할 수 있습니다.

C++23의 std::ranges::lazy_split_view 사용 예제

#include <ranges>
#include <string_view>
#include <iostream>

int main() {
    std::string_view text = "C++23:Lazy:Split:View:Example";
    auto lsv = std::ranges::lazy_split_view(text, ':');

    // 여기서 lsv는 필요할 때마다 다음 부분 범위를 계산
    // 즉, 전체를 한 번에 나누지 않고, 순회 과정에서 지연된 처리 수행

    for (auto part : lsv) {
        // part는 요청되는 시점에 필요한 부분 범위를 계산
        std::cout << std::string_view(part) << '\n';
    }

    return 0;
}
  • lazy_split_view는 부분 범위를 요구하는 시점에만 실제 split 연산을 수행합니다.
  • 이를 통해 메모리 사용량과 초기 처리 비용을 절감하고, 특히 굉장히 긴 문자열 혹은 지연된 I/O를 하는 스트림 처리에도 유리합니다.

어떻게 좋아졌나요?

  • 지연(lazy) 처리: 필요할 때만 구분 연산을 수행하므로, 방대한 데이터나 느린 I/O 소스 처리 시 효율적
  • 메모리 사용량 절감: 미리 전체 구분 결과를 계산하지 않아도 되므로, 메모리 사용이 감소
  • 유연한 파이프라인 구성: 다른 범위 어댑터나 변환과 쉽게 조합 가능하여, 대규모 데이터 처리 파이프라인을 효율적으로 구성

상세한 예제와 비교

1. 큰 파일 처리

파일에서 한 줄씩 읽어와 ':' 기준으로 구분한다고 해봅시다.

#include <ranges>
#include <string>
#include <fstream>
#include <iostream>

int main() {
    std::ifstream file("large_data.txt");
    std::string line;

    while (std::getline(file, line)) {
        auto lsv = std::ranges::lazy_split_view(line, ':');
        // 여기서 line을 ':'로 나눌 때, 모든 line을 전부 split하지 않고,
        // part를 요청하는 순간에만 구분

        for (auto part : lsv) {
            std::cout << std::string(part) << '\n'; 
        }
    }

    return 0;
}
  • 엄청나게 긴 파일이라도 한 줄씩, 한 부분씩 처리하므로 성능과 메모리를 효율적으로 사용할 수 있습니다.

2. 다른 뷰와 조합

lazy_split_view는 다른 범위 어댑터(filter, transform, take, drop 등)와 함께 사용 가능하여, 복잡한 데이터 처리 파이프라인을 쉽게 구축할 수 있습니다.

#include <ranges>
#include <string_view>
#include <iostream>

int main() {
    std::string_view text = "Apple:Banana::Cherry:::Date";
    auto lsv = std::ranges::lazy_split_view(text, ':')
               | std::views::filter([](auto part) { return !part.empty(); });

    for (auto part : lsv) {
        std::cout << std::string_view(part) << '\n';
    }
    // 빈 부분은 필터링되어 출력 안 함: Apple, Banana, Cherry, Date
    return 0;
}

주의 사항

  • 요구사항 충족 필요: lazy_split_view는 적절한 iterator category와 sentinel이 있는 범위를 대상으로 동작합니다.
  • 컴파일러 및 라이브러리 지원: C++23 기능이므로, 이를 지원하는 컴파일러와 표준 라이브러리가 필요합니다.
  • 지연 특성을 잘 이해: 지연 처리가 항상 더 빠르거나 더 나은 것은 아님. 특정 상황(매우 큰 데이터, 부분 접근)에 유리하므로 상황에 맞는 사용 필요.

요약

C++23의 std::ranges::lazy_split_view는 기존의 split_view와 유사한 기능을 제공하지만, 지연(lazy) 특성을 통해 필요할 때마다 구분 연산을 수행하는 더 효율적인 데이터 처리 방식을 제공합니다. 이를 통해 메모리 사용량을 줄이고 대규모 데이터 처리 시 성능을 향상시킬 수 있으며, 다른 범위 어댑터와 조합하여 복잡한 파이프라인을 구축하는 데 활용할 수 있습니다.

 

참고 자료:

반응형