본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : std::generate, local static variables (98, 11, 14)

by snowoods 2025. 9. 9.

Modern C++

std::generate, 지역 정적 변수 (Local Static Variables)

 

개요

std::generate는 C++ 표준 라이브러리의 <algorithm> 헤더에 포함된 함수로, 지정된 범위의 모든 요소에 특정 함수(제너레이터)를 호출하여 반환된 값을 채워 넣는 역할을 합니다. 이 과정에서 제너레이터 함수 내부에 지역 정적 변수(Local Static Variables)를 활용하면, 함수 호출 간에 상태를 유지해야 하는 경우 매우 유용합니다.

본 문서에서는 std::generate를 사용하여 벡터를 임의의 값으로 채우는 예제를 통해, 제너레이터 함수 내에서 지역 정적 변수가 어떻게 상태를 보존하고 효율적인 코드 작성에 기여하는지 설명합니다.

 

C++ 버전별 주요 키워드 도입 시기

  • C++98: std::generate, static (지역 변수)
  • C++11: auto, constexpr, <random> 헤더 (std::random_device, std::mt19937, std::uniform_int_distribution), std::int32_t 등 고정 너비 정수 타입
  • C++14: 숫자 구분자 (Digit Separator) - 예: 1'000'000U

 

내용 설명

std::generate

std::generate 함수는 시작 반복자(first)와 끝 반복자(last)로 정의되는 범위 [first, last)에, 인자 없는 제너레이터 함수 g가 생성하는 값을 순차적으로 할당합니다.

void generate(ForwardIt first, ForwardIt last, Generator g);

지역 정적 변수 (Local Static Variables)

함수 내부에 static 키워드로 선언된 변수를 지역 정적 변수라고 합니다. 이 변수들은 다음과 같은 특징을 가집니다.

  1. 단 한 번의 초기화: 프로그램 실행 중 해당 변수의 선언문을 처음 만났을 때 단 한 번만 초기화됩니다.
  2. 생명 주기(Life Cycle): 프로그램이 시작될 때 생성되고 종료될 때 파괴됩니다. 즉, 함수 호출이 끝나도 값이 소멸되지 않고 유지됩니다.
  3. 범위(Scope): 변수가 선언된 함수 블록 내에서만 접근할 수 있습니다.

예제 코드의 gen 함수에서 seed, g, d는 모두 지역 정적 변수입니다. 이 덕분에 gen 함수가 여러 번 호출되더라도 난수 생성기와 분포 객체가 반복적으로 생성 및 초기화되는 오버헤드를 피할 수 있습니다. 상태가 유지되므로 매번 다른 난수를 효율적으로 생성할 수 있습니다.

 

예제 코드

#include <algorithm>
#include <cstdint>
#include <iostream>
#include <random>
#include <vector>

namespace
{
constexpr auto NUM_ELEMENTS = size_t{1'000'000U};
}; // namespace

// 난수 생성을 위한 제너레이터 함수
std::int32_t gen()
{
    // 지역 정적 변수: gen()이 처음 호출될 때 단 한 번만 초기화됩니다.
    static auto seed = std::random_device{};
    static auto g = std::mt19937{seed()};
    static auto d = std::uniform_int_distribution<std::int32_t>{-10, 10};

    return d(g);
}

// 벡터 내용을 출력하는 템플릿 함수
template <typename T>
void print_vector(const std::vector<T> &vec)
{
    for (const auto val : vec)
    {
        std::cout << val << '\n';
    }
    std::cout << '\n';
}

int main()
{
    auto my_vector = std::vector<std::int32_t>(NUM_ELEMENTS, 0U);

    // my_vector의 시작부터 끝까지 gen() 함수가 반환하는 값으로 채웁니다.
    std::generate(my_vector.begin(), my_vector.end(), gen);

    // 생성된 벡터의 처음 10개 요소만 출력하여 확인합니다.
    std::cout << "Generated first 10 elements:" << '\n';
    for (size_t i = 0; i < 10; ++i)
    {
        std::cout << my_vector[i] << ' ';
    }
    std::cout << '\n';

    return 0;
}

 

실행 결과

Generated first 10 elements:
-1 5 8 -4 10 2 -7 3 0 9 

실행 시마다 결과는 달라질 수 있습니다.

 

활용팁

  • 상태 유지: std::generate에 전달되는 제너레이터가 호출 횟수나 이전 값과 같은 상태를 기억해야 할 때 지역 정적 변수는 훌륭한 해결책입니다. 예를 들어, 1, 2, 3, ... 과 같이 순차적으로 증가하는 값을 생성하는 제너레이터를 쉽게 만들 수 있습니다.
  • 성능 최적화: 난수 생성기, 파일 핸들, 데이터베이스 연결 등 생성 비용이 비싼 객체를 제너레이터 내에서 반복적으로 생성하지 않고 static으로 선언하면 성능을 크게 향상시킬 수 있습니다.
  • 스레드 안전성(Thread-Safety): C++11부터 지역 정적 변수의 초기화는 스레드에 안전하게(thread-safe) 이루어집니다. 여러 스레드에서 동시에 제너레이터 함수를 호출하더라도 객체는 정확히 한 번만 초기화됩니다. 단, 초기화 이후 객체 자체에 대한 접근은 스레드에 안전하지 않을 수 있으므로 주의가 필요합니다. (예: std::mt19937은 스레드에 안전하지 않습니다.)