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

Modern C++ : 유틸리티 함수 활용 (11, 17)

by snowoods 2025. 9. 15.

Modern C++

모던 C++ 유틸리티 함수 활용

 

개요

utils.h 헤더 파일에 정의된 다양한 유틸리티 함수를 활용하여 C++11, C++17, C++20 등 모던 C++의 주요 표준 기능들을 사용하는 방법을 알아봅니다. 템플릿, constexpr, std::string_view, 구조적 바인딩(structured bindings) 등 코드의 재사용성과 가독성, 성능을 높여주는 기능들을 실제 예제를 통해 학습합니다.

 

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

  • C++11
    • constexpr: 컴파일 타임에 값을 계산할 수 있는 상수 표현식입니다.
    • std::array: 정적 크기의 배열을 위한 컨테이너입니다.
    • std::vector, std::map: 개선된 표준 라이브러리 컨테이너입니다.
    • 범위 기반 for 루프 (Range-based for loop): 컨테이너의 모든 요소를 쉽게 순회할 수 있습니다.
    • 템플릿 기능 향상: 가변 인자 템플릿 등
  • C++17
    • std::string_view: 문자열을 소유하지 않고 "뷰"만 제공하여 불필요한 복사를 방지하고 성능을 향상시킵니다.
    • 구조적 바인딩 (Structured bindings): std::pair, std::tuple, 구조체 등의 멤버를 간결하게 개별 변수로 분해할 수 있습니다.
    • if constexpr: 컴파일 타임에 if 문의 분기를 결정할 수 있습니다.

 

내용 설명

utils.hmain.cpp 예제는 다음과 같은 모던 C++의 핵심 기능들을 보여줍니다.

  1. 템플릿 함수 (Template Functions)
    • print_array, print_vector 함수는 템플릿을 사용하여 다양한 데이터 타입(e.g., int, double)의 배열과 벡터를 출력할 수 있습니다. 이는 코드 중복을 줄이고 일반적인(generic) 코드를 작성하게 해줍니다.
    • print_vectorstd::vector<std::pair<std::string, std::size_t>> 타입에 대해 템플릿 특수화(template specialization) 되어 있어, 해당 타입의 벡터를 특별한 형식으로 출력합니다.
  2. constexpr 상수
    • pi 값을 constexpr로 선언하여 컴파일 타임 상수로 만들었습니다. 이를 통해 런타임 비용 없이 값을 사용할 수 있으며, 컴파일러가 더 많은 최적화를 수행할 수 있습니다.
  3. 구조적 바인딩 (Structured Bindings)
    • print_map 함수 내의 for (const auto &[Key, value] : Map) 구문은 C++17의 구조적 바인딩을 사용합니다. std::map의 각 원소(std::pair)를 Keyvalue라는 두 개의 변수로 즉시 분해하여 코드 가독성을 크게 향상시킵니다.
  4. std::string_view
    • readFile 함수는 파일 경로를 std::string_view로 받습니다. std::string 객체를 생성하고 복사하는 대신, 기존 문자열 데이터를 가리키는 뷰를 사용하므로 오버헤드가 적습니다. 파일 경로처럼 읽기만 하는 문자열을 전달할 때 매우 효율적입니다.

 

예제 코드

src/utils.h

#ifndef UTILS_H
#define UTILS_H

#include <array>
#include <cmath>
#include <fstream>
#include <iostream>
#include <map>
#include <random>
#include <sstream>
#include <string_view>
#include <unordered_map>
#include <vector>

constexpr double pi = 3.14159265358979311600;

template <typename T>
void print_array(const T *array, const std::size_t length)
{
    for (std::size_t i = 0; i < length - 1; i++)
    {
        std::cout << array[i] << ", ";
    }

    std::cout << array[length - 1] << '\n';
}

template <typename T, std::size_t S>
void print_array(const std::array<T, S> array)
{
    for (std::size_t i = 0; i < array.size() - 1; i++)
    {
        std::cout << array[i] << ", ";
    }

    std::cout << array[array.size() - 1] << '\n';
}

template <typename T>
void print_vector(const std::vector<T> &vector)
{
    for (std::size_t i = 0; i < vector.size() - 1; i++)
    {
        std::cout << vector[i] << ", ";
    }

    std::cout << vector[vector.size() - 1] << '\n';
}

template <>
void print_vector(
    const std::vector<std::pair<std::string, std::size_t>> &vector)
{
    for (std::size_t i = 0; i < vector.size() - 1; i++)
    {
        std::cout << vector[i].first << ": " << vector[i].second << ", ";
    }

    std::cout << vector[vector.size() - 1].first << ": "
              << vector[vector.size() - 1].second << '\n';
}

template <typename T, typename U>
void print_map(const std::map<T, U> Map)
{
    for (const auto &[Key, value] : Map)
    {
        std::cout << Key << ": " << value << '\n';
    }
}

std::string readFile(std::string_view file_path)
{
    auto str = std::string{};
    auto text = std::string{};

    auto iffile = std::ifstream{};
    iffile.open(file_path.data());

    if (iffile.is_open())
    {
        while (std::getline(iffile, str))
        {
            text += str + '\n';
        }
    }

    iffile.close();

    return text;
}


template <typename T>
void random_vector(std::vector<T> &vec)
{
    std::mt19937 random_generator(22);
    std::uniform_int_distribution<T> random_distribution(-10, 10);

    for (auto &val : vec)
    {
        val = random_distribution(random_generator);
    }
}

void clear_console()
{
#if defined _WIN32
    system("cls");
#elif defined(__LINUX__) || defined(__gnu_linux__) || defined(__linux__)
    system("clear");
#elif defined(__APPLE__)
    system("clear");
#else
    system("clear");
#endif
}

#endif /* UTILS_H */

src/main.cpp

#include "utils.h"

int main()
{
    // 1. C-style array and std::array with templates
    std::cout << "1. C-style array and std::array with templates" << std::endl;
    int c_style_arr[] = {1, 2, 3, 4, 5};
    std::array<double, 5> std_arr = {1.1, 2.2, 3.3, 4.4, 5.5};
    print_array(c_style_arr, 5);
    print_array(std_arr);
    std::cout << std::endl;

    // 2. std::vector with template specialization
    std::cout << "2. std::vector with template specialization" << std::endl;
    std::vector<int> int_vec = {10, 20, 30};
    std::vector<std::pair<std::string, std::size_t>> pair_vec = {{"apple", 5}, {"banana", 3}};
    print_vector(int_vec);
    print_vector(pair_vec);
    std::cout << std::endl;

    // 3. std::map with structured bindings (C++17)
    std::cout << "3. std::map with structured bindings (C++17)" << std::endl;
    std::map<std::string, int> fruits = {
        {"orange", 8},
        {"grape", 2}
    };
    print_map(fruits);
    std::cout << std::endl;

    // 4. constexpr value
    std::cout << "4. constexpr value" << std::endl;
    double circle_area = pi * 10 * 10;
    std::cout << "Area of circle with radius 10: " << circle_area << std::endl;
    std::cout << std::endl;

    // 5. random_vector
    std::cout << "5. random_vector" << std::endl;
    std::vector<int> random_numbers(5);
    random_vector(random_numbers);
    print_vector(random_numbers);
    std::cout << std::endl;

    // 6. readFile with std::string_view (C++17)
    std::cout << "6. readFile with std::string_view (C++17)" << std::endl;
    // Create a dummy file to read
    std::ofstream outfile("test.txt");
    outfile << "Hello from test.txt!" << std::endl;
    outfile << "This is the second line." << std::endl;
    outfile.close();
    std::cout << readFile("test.txt") << std::endl;

    return 0;
}

 

실행 결과

1. C-style array and std::array with templates
1, 2, 3, 4, 5
1.1, 2.2, 3.3, 4.4, 5.5

2. std::vector with template specialization
10, 20, 30
apple: 5, banana: 3

3. std::map with structured bindings (C++17)
grape: 2
orange: 8

4. constexpr value
Area of circle with radius 10: 314.159

5. random_vector
-5, 8, -4, -1, 10

6. readFile with std::string_view (C++17)
Hello from test.txt!
This is the second line.

 

활용팁

  • 템플릿과 if constexpr 결합: C++17의 if constexpr를 템플릿 함수 내에서 사용하면, 특정 타입에 대해서만 컴파일되는 코드를 작성할 수 있어 더욱 유연하고 최적화된 템플릿을 만들 수 있습니다.
  • 다양한 컨테이너에 print 함수 확장: print_vector와 유사한 템플릿 함수를 std::list, std::deque 등 다른 표준 컨테이너에 대해서도 작성하면 디버깅 시 유용하게 사용할 수 있습니다.
  • 파일 I/O 성능: readFile 함수는 간단한 예제이지만, 매우 큰 파일을 다룰 때는 메모리 문제를 피하기 위해 파일을 한 번에 모두 읽는 대신 한 줄씩 또는 청크(chunk) 단위로 읽고 처리하는 것이 좋습니다.