C++ STL 정리 (3편) — C++17/20 유틸리티

2026. 6. 1. 16:07·블로그, 컴퓨터/Cheatsheets

C++ STL cheatsheet

C++17과 C++20을 거치면서 표준 라이브러리에 실용적인 타입들이 많이 추가됐습니다. 외부 라이브러리 없이도 "값이 없을 수 있음", "여러 타입 중 하나", "파일 시스템 조작" 같은 걸 표준으로 처리할 수 있게 됐습니다.

이 편에서는 그 타입들을 한데 모아 정리합니다.


std::optional (C++17)

값이 있을 수도 없을 수도 있는 타입입니다. null 포인터나 -1 같은 sentinel 값 대신 의도를 명확하게 표현합니다.

#include <optional>

// 반환 타입으로 — "찾으면 값, 없으면 nullopt"
std::optional<int> find_index(const std::vector<int>& v, int target) {
    for (int i = 0; i < v.size(); ++i)
        if (v[i] == target) return i;
    return std::nullopt;
}

auto idx = find_index(vec, 42);

// 확인 후 사용
if (idx) {
    fmt::print("위치: {}\n", *idx);
}
if (idx.has_value()) { ... }

// 값 꺼내기
*idx;                         // 역참조 (없으면 UB)
idx.value();                  // 없으면 std::bad_optional_access
idx.value_or(-1);             // 없으면 기본값

// 수정
idx = 10;
idx.reset();                  // nullopt로
idx.emplace(20);              // 직접 생성

모나딕 연산 (C++23)

// and_then — 값이 있을 때만 함수 적용, optional 반환
auto result = find_index(vec, 42)
    .and_then([&](int i) -> std::optional<std::string> {
        if (i < names.size()) return names[i];
        return std::nullopt;
    });

// transform — 값이 있을 때만 변환
auto doubled = find_index(vec, 42)
    .transform([](int i) { return i * 2; });

// or_else — 없을 때 대체 optional 제공
auto fallback = find_index(vec, 42)
    .or_else([]() { return std::optional<int>{0}; });

std::variant (C++17)

여러 타입 중 정확히 하나를 담는 타입 안전 union입니다. union과 달리 어떤 타입이 들어있는지 런타임에 추적합니다.

#include <variant>

std::variant<int, double, std::string> v = 42;

// 타입 확인
std::holds_alternative<int>(v);       // true
v.index();                            // 0 (int의 인덱스)

// 값 꺼내기
std::get<int>(v);                     // 42
std::get<0>(v);                       // 인덱스로도 가능
std::get_if<int>(&v);                 // 포인터 반환, 틀리면 nullptr

// 변경
v = 3.14;
v = "hello";
v.emplace<std::string>("world");

// visit — 타입마다 다른 처리
std::visit([](auto&& val) {
    using T = std::decay_t<decltype(val)>;
    if constexpr (std::is_same_v<T, int>)
        fmt::print("int: {}\n", val);
    else if constexpr (std::is_same_v<T, double>)
        fmt::print("double: {}\n", val);
    else
        fmt::print("string: {}\n", val);
}, v);

// 오버로드 패턴 (자주 쓰는 관용구)
template<class... Ts> struct overload : Ts... { using Ts::operator()...; };

std::visit(overload{
    [](int i)               { fmt::print("int {}\n", i); },
    [](double d)            { fmt::print("double {}\n", d); },
    [](const std::string& s){ fmt::print("string {}\n", s); },
}, v);

에러 처리 패턴

using Result = std::variant<std::string, std::error_code>;

Result read_file(const std::string& path) {
    std::ifstream f(path);
    if (!f) return std::make_error_code(std::errc::no_such_file_or_directory);
    return std::string{std::istreambuf_iterator<char>(f), {}};
}

auto r = read_file("config.txt");
std::visit(overload{
    [](const std::string& s) { fmt::print("내용: {}\n", s); },
    [](std::error_code ec)   { fmt::print("에러: {}\n", ec.message()); },
}, r);

std::any (C++17)

타입 정보를 지우고 어떤 값이든 담을 수 있는 타입입니다. variant와 달리 담을 타입을 미리 지정하지 않아도 됩니다.

#include <any>

std::any a = 42;
a = 3.14;
a = std::string("hello");

// 타입 확인
a.type() == typeid(std::string);
a.has_value();

// 값 꺼내기
std::any_cast<std::string>(a);          // 복사
std::any_cast<std::string&>(a);         // 참조
std::any_cast<std::string*>(&a);        // 포인터, 실패 시 nullptr

// 실패 시 예외
try {
    std::any_cast<int>(a);              // std::bad_any_cast
} catch (const std::bad_any_cast& e) { ... }

a.reset();                              // 비우기

any는 타입 안전성이 런타임으로 미뤄지기 때문에, 타입을 미리 알 수 있는 상황에선 variant가 더 낫습니다. 플러그인 시스템, 설정 값 저장처럼 타입이 열려있는 경우에 씁니다.


std::string_view (C++17)

문자열의 비소유(non-owning) 뷰입니다. 복사 없이 문자열을 참조합니다.

#include <string_view>

// 함수 인수 — const std::string& 대신 쓰면 불필요한 복사 없음
void print(std::string_view sv) {
    fmt::print("{} (len={})\n", sv, sv.size());
}

std::string s = "Hello, World";
print(s);                         // std::string → string_view (복사 없음)
print("literal");                 // const char* → string_view (복사 없음)
print(s.substr(0, 5));           // 임시 string → string_view

// 부분 문자열 (복사 없음)
std::string_view sv = s;
sv.substr(7, 5);                  // "World" — 새 string 없이 뷰만

// 주요 연산
sv.starts_with("Hello");          // C++20
sv.ends_with("World");            // C++20
sv.find("World");
sv.remove_prefix(7);              // sv 앞부분 제거 (원본 안 바뀜)
sv.remove_suffix(1);

// 주의: string_view는 수명이 원본에 묶임
std::string_view dangling() {
    std::string s = "temp";
    return s;                     // 위험! s가 소멸됨
}

std::span (C++20)

배열이나 연속 메모리의 비소유 뷰입니다. string_view의 배열 버전이라 생각하면 됩니다.

#include <span>

// 함수에서 배열/벡터/포인터 통일 처리
void process(std::span<int> data) {
    for (auto& x : data) x *= 2;
    fmt::print("size: {}\n", data.size());
}

std::vector<int> v = {1, 2, 3, 4, 5};
int arr[] = {1, 2, 3};

process(v);                       // vector
process(arr);                     // C 배열
process({arr, 3});                // 포인터 + 크기

// 부분 뷰 (복사 없음)
std::span<int> sp(v);
sp.subspan(1, 3);                 // [1, 3) 범위
sp.first(2);                      // 앞 2개
sp.last(2);                       // 뒤 2개

// 고정 크기 span
std::span<int, 3> fixed(arr);    // 컴파일 타임 크기

// 읽기 전용
std::span<const int> ro(v);

std::filesystem (C++17)

파일과 디렉토리를 다루는 표준 라이브러리입니다.

#include <filesystem>
namespace fs = std::filesystem;

// 경로 조작
fs::path p = "/home/user/projects";
p / "myapp" / "src";              // 경로 이어붙이기
p.filename();                     // "projects"
p.parent_path();                  // "/home/user"
p.extension();                    // 확장자
p.stem();                         // 확장자 없는 파일명
p.string();                       // std::string으로
p.is_absolute();
p.is_relative();

// 존재/타입 확인
fs::exists(p);
fs::is_regular_file(p);
fs::is_directory(p);
fs::is_symlink(p);
fs::file_size(p);                 // 바이트

// 디렉토리 조작
fs::create_directory(p);
fs::create_directories(p);       // 중간 경로 포함
fs::remove(p);                   // 파일 또는 빈 디렉토리
fs::remove_all(p);               // 디렉토리 통째로
fs::copy(src, dst);
fs::copy(src, dst, fs::copy_options::overwrite_existing);
fs::rename(old_p, new_p);

// 임시 경로
auto tmp = fs::temp_directory_path();
auto tmp_file = tmp / "myapp_XXXXXX";

// 현재 경로
fs::current_path();
fs::current_path(new_path);      // 변경

// 순회
for (const auto& e : fs::directory_iterator(p)) {
    fmt::print("{} {}\n",
        e.path().filename().string(),
        fs::is_directory(e) ? "[DIR]" : "");
}

// 재귀 순회
for (const auto& e : fs::recursive_directory_iterator(p)) {
    if (e.path().extension() == ".cpp")
        fmt::print("{}\n", e.path().string());
}

// 에러 처리 (예외 없이)
std::error_code ec;
fs::create_directory(p, ec);
if (ec) spdlog::error("디렉토리 생성 실패: {}", ec.message());

std::expected (C++23)

함수가 성공하면 값을, 실패하면 에러를 반환하는 타입입니다. optional과 비슷하지만 실패 이유도 담습니다. Rust의 Result<T, E>에 해당합니다.

#include <expected>

// 반환 타입
std::expected<int, std::string> parse(std::string_view s) {
    try {
        return std::stoi(std::string(s));
    } catch (...) {
        return std::unexpected("파싱 실패: " + std::string(s));
    }
}

auto result = parse("42");

// 확인
if (result) {
    fmt::print("값: {}\n", *result);
} else {
    fmt::print("에러: {}\n", result.error());
}

result.value();                   // 없으면 std::bad_expected_access
result.value_or(0);              // 에러면 기본값
result.error();                  // 에러 값 (에러 상태일 때만)

// 모나딕 연산 (C++23)
auto doubled = parse("21")
    .and_then([](int n) -> std::expected<int, std::string> {
        if (n < 0) return std::unexpected("음수 불가");
        return n * 2;
    })
    .transform([](int n) { return fmt::format("결과: {}", n); })
    .or_else([](const std::string& e) {
        return std::expected<std::string, std::string>{"기본값"};
    });

정리 표

타입 표준 핵심 용도
std::optional<T> C++17 값이 없을 수 있음
std::variant<Ts...> C++17 타입 안전 union
std::any C++17 타입 소거 컨테이너
std::string_view C++17 문자열 비소유 뷰
std::span<T> C++20 배열 비소유 뷰
std::filesystem C++17 파일/디렉토리 조작
std::expected<T,E> C++23 값 또는 에러 반환

언제 뭘 쓰나

상황 선택
값이 없을 수 있는 반환 optional
성공/실패 + 에러 정보 expected
제한된 타입 집합 중 하나 variant
타입 미정 값 저장 any
문자열 인수로 받기 string_view
배열 인수로 받기 span
파일/경로 처리 filesystem
반응형
저작자표시 비영리 변경금지 (새창열림)

'블로그, 컴퓨터 > Cheatsheets' 카테고리의 다른 글

C++ STL 정리 (2편) — 알고리즘, 이터레이터, 유틸리티  (0) 2026.06.01
C++ STL 정리 (1편) — 컨테이너  (0) 2026.06.01
GitHub Actions 정리 (2편) — 캐싱, Matrix, 재사용, 실전 패턴  (0) 2026.05.31
GitHub Actions 정리 (1편) — 개념, 워크플로우 구조, 트리거  (0) 2026.05.31
CMake 정리 (2편) — 서브디렉토리, 외부 라이브러리, 실용 패턴  (0) 2026.05.30
'블로그, 컴퓨터/Cheatsheets' 카테고리의 다른 글
  • C++ STL 정리 (2편) — 알고리즘, 이터레이터, 유틸리티
  • C++ STL 정리 (1편) — 컨테이너
  • GitHub Actions 정리 (2편) — 캐싱, Matrix, 재사용, 실전 패턴
  • GitHub Actions 정리 (1편) — 개념, 워크플로우 구조, 트리거
생각사람
생각사람
지극히 사적인 연구실
  • 생각사람
    생각사람의 별장
    생각사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (207)
      • 금융 (57)
        • 주식 공부 (11)
        • 파생상품 입문 (17)
        • 파생상품 기초 (15)
        • 파생상품 실전 (14)
      • 블로그, 컴퓨터 (83)
        • 프로그래밍 (16)
        • DevOps (8)
        • AI, RL, ML, ... (5)
        • 애드센스, SEO (23)
        • 임베디드 (3)
        • 컴퓨터 관련 (7)
        • Cheatsheets (21)
      • 다른 공부들 (67)
        • 읽고 쓰기 (18)
        • 수학 (15)
        • 물리 (9)
        • 사진 공부 (25)
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 태그

    소니 a6000
    코딩
    version control
    프로그래밍
    c
    벡터
    옵션
    깃
    구글 애드센스
    독후감
    행렬
    공업수학
    Kreyszig
    github
    스트랭글
    오펜하이머
    cmake
    파생상품
    선형대수학
    슈뢰딩거 방정식
    깃허브
    CheatSheet
    선물 옵션
    양자역학
    웹크롤러
    옵션 투자
    AI
    스트래들
    c++
    GIT
  • hELLO· Designed By정상우.v4.10.6
생각사람
C++ STL 정리 (3편) — C++17/20 유틸리티
상단으로

티스토리툴바