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

C++23에서는 비동기 프로그래밍과 지연 계산을 더욱 편리하게 구현할 수 있도록 std::generator가 도입되었습니다. 이번 글에서는 std::generator의 개념과 사용법, 그리고 이전 버전과 비교하여 어떻게 개선되었는지 알아보겠습니다.

std::generator란 무엇인가요?

std::generator는 C++23에서 도입된 코루틴(coroutine) 기반의 제네레이터로, 지연된 값의 시퀀스를 순차적으로 생성하고 반환할 수 있는 기능입니다. 이를 통해 복잡한 데이터 생성 로직을 간결하고 효율적으로 구현할 수 있으며, 특히 범위 기반 for 루프와 함께 사용하여 자연스러운 반복 처리를 할 수 있습니다.

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

C++20에서는 코루틴이 도입되었지만, 이를 직접 사용하기 위해서는 복잡한 프레임워크를 구축하거나 서드파티 라이브러리를 사용해야 했습니다.

예제: 기존 방식의 제네레이터 구현

#include <vector>
#include <functional>

std::vector<int> generateNumbers(int n) {
    std::vector<int> numbers;
    for (int i = 0; i < n; ++i) {
        numbers.push_back(i);
    }
    return numbers;
}

int main() {
    for (int num : generateNumbers(10)) {
        std::cout << num << ' ';
    }
    return 0;
}
  • 문제점:
    • 모든 값을 한 번에 생성하여 벡터에 저장하므로 메모리 오버헤드가 발생합니다.
    • 지연 계산이 아니므로, 필요한 만큼만 값을 생성할 수 없습니다.
    • 복잡한 데이터 생성 로직을 표현하기 어렵습니다.

C++23의 std::generator를 사용한 개선

std::generator를 사용하면 코루틴을 통해 필요한 시점에 값을 생성하여 반환할 수 있습니다.

예제: std::generator 사용

#include <generator>
#include <iostream>

std::generator<int> generateNumbers(int n) {
    for (int i = 0; i < n; ++i) {
        co_yield i;
    }
}

int main() {
    for (int num : generateNumbers(10)) {
        std::cout << num << ' ';
    }
    return 0;
}
  • co_yield를 사용하여 값을 순차적으로 생성합니다.
  • 범위 기반 for 루프에서 자연스럽게 사용할 수 있습니다.
  • 메모리 오버헤드가 적고, 지연 계산이 가능합니다.

어떻게 좋아졌나요?

  • 지연 계산 지원: 필요한 시점에 값을 생성하므로 메모리 사용량을 최소화할 수 있습니다.
  • 코드 간결화: 복잡한 데이터 생성 로직을 간결하고 직관적으로 표현할 수 있습니다.
  • 범위 기반 for 루프와 호환: 표준 반복자 패턴을 따르므로 사용이 편리합니다.
  • 성능 향상: 불필요한 데이터 생성과 메모리 할당을 피하여 성능을 최적화할 수 있습니다.

상세한 예제와 비교

1. 피보나치 수열 생성

기존 방식

std::vector<int> generateFibonacci(int n) {
    std::vector<int> sequence;
    int a = 0, b = 1;
    while (n-- > 0) {
        sequence.push_back(a);
        int next = a + b;
        a = b;
        b = next;
    }
    return sequence;
}

int main() {
    for (int num : generateFibonacci(10)) {
        std::cout << num << ' ';
    }
    return 0;
}
  • 모든 피보나치 수를 벡터에 저장하므로 메모리 오버헤드 발생

C++23 방식

#include <generator>

std::generator<int> generateFibonacci(int n) {
    int a = 0, b = 1;
    while (n-- > 0) {
        co_yield a;
        int next = a + b;
        a = b;
        b = next;
    }
}

int main() {
    for (int num : generateFibonacci(10)) {
        std::cout << num << ' ';
    }
    return 0;
}
  • 지연 계산을 통해 메모리 사용을 최소화

2. 파일에서 라인 단위로 읽기

#include <generator>
#include <fstream>
#include <string>

std::generator<std::string> readLines(const std::string& filename) {
    std::ifstream file(filename);
    std::string line;
    while (std::getline(file, line)) {
        co_yield line;
    }
}

int main() {
    for (const auto& line : readLines("data.txt")) {
        std::cout << line << '\n';
    }
    return 0;
}
  • 큰 파일을 처리할 때 메모리 사용량을 줄이고, 필요한 만큼만 데이터를 읽어올 수 있습니다.

주의 사항

  • 컴파일러 지원: std::generator는 C++23 기능이므로, 이를 지원하는 컴파일러와 표준 라이브러리가 필요합니다.
  • 코루틴 이해 필요: 코루틴의 동작 원리를 이해하고 사용하는 것이 좋습니다.
  • 예외 처리: 코루틴 내에서 예외가 발생하면 예외 처리를 적절히 해야 합니다.

std::generator와 std::ranges의 조합

std::generator는 std::ranges와 함께 사용할 수 있어 더욱 강력한 기능을 제공합니다.

#include <generator>
#include <ranges>

auto evenNumbers(int start, int end) {
    for (int i = start; i <= end; ++i) {
        if (i % 2 == 0) {
            co_yield i;
        }
    }
}

int main() {
    auto evens = evenNumbers(1, 20) | std::views::take(5);

    for (int num : evens) {
        std::cout << num << ' ';
    }
    return 0;
}
  • std::views와 함께 사용하여 필터링, 슬라이싱 등 다양한 조작이 가능합니다.

요약

C++23의 std::generator는 코루틴을 기반으로 한 제네레이터로, 지연된 값의 시퀀스를 효율적으로 생성하고 처리할 수 있게 해줍니다. 이전에는 복잡한 코드나 외부 라이브러리를 사용해야 했지만, 이제는 표준 라이브러리만으로 간결하고 성능 효율적인 코드를 작성할 수 있습니다. 이를 통해 비동기 프로그래밍, 데이터 스트림 처리 등 다양한 분야에서 개발자의 생산성을 높일 수 있습니다.

 

 

참고 자료:

 

반응형