Menu
Coddy logo textTech

optional, variant, any

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 89번째.

현대적인 C++은 값이 없을 수 있는 경우를 처리하거나, 여러 유형 중 하나를 보유하거나, 어떤 유형이든 저장할 수 있도록 도와주는 세 가지 어휘 유형을 <optional>, <variant><any> 헤더에서 제공합니다.

std::optional<T>는 존재할 수도 있고 존재하지 않을 수도 있는 값을 나타냅니다. 이는 예외를 던지지 않고 실패할 수 있는 함수에 적합합니다:

#include <iostream>
#include <optional>

std::optional<int> findIndex(const std::string& str, char c) {
    for (size_t i = 0; i < str.size(); ++i) {
        if (str[i] == c) return i;
    }
    return std::nullopt;  // 값이 없음
}

int main() {
    auto result = findIndex("hello", 'l');
    if (result.has_value()) {
        std::cout << "Found at: " << *result << "\n";  // 2
    }
}

std::variant<Types...>는 지정된 타입 중 정확히 하나를 언제든지 보유할 수 있는 타입 안전 유니온(type-safe union)입니다:

#include <iostream>
#include <variant>
#include <string>

int main() {
    std::variant<int, double, std::string> data;
    
    data = 42;
    std::cout << std::get<int>(data) << "\n";
    
    data = "hello";
    if (std::holds_alternative<std::string>(data)) {
        std::cout << std::get<std::string>(data) << "\n";
    }
}

std::any는 런타임에 결정되는 모든 타입의 값을 보유할 수 있습니다. 값을 가져오려면 std::any_cast를 사용하세요:

#include <iostream>
#include <any>

int main() {
    std::any value = 10;
    std::cout << std::any_cast<int>(value) << "\n";
    
    value = std::string("text");
    std::cout << std::any_cast<std::string>(value) << "\n";
}

null이 될 수 있는 값에는 std::optional을, 컴파일 타임에 가능한 타입들을 알고 있을 때는 std::variant를, 그리고 진정으로 런타임 타입 유연성이 필요할 때만 std::any를 선택하세요.

challenge icon

챌린지

쉬움

현대적인 C++ 어휘 타입(vocabulary types)의 강력함을 보여주는 설정 시스템을 구축해 보겠습니다. 값이 없을 수도 있거나, 서로 다른 타입을 가질 수 있거나, 런타임에 타입 유연성이 필요한 값들을 처리하는 유연한 설정 관리자를 만들 것입니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • ConfigTypes.h: 설정 값 타입과 헬퍼 함수를 정의합니다.

    문자열을 받아 정수로 파싱을 시도하는 parseValue 함수를 만드세요. 문자열이 유효한 정수를 나타내면 std::optional로 감싼 정수를 반환합니다. 파싱에 실패하면(문자열에 숫자가 아닌 문자가 포함된 경우) std::nullopt를 반환합니다. 단순화를 위해, 문자열이 숫자만 포함하거나 선택적으로 맨 앞에 마이너스 기호가 있는 경우만 유효한 것으로 간주합니다.

    또한 std::variant를 사용하여 int, double, 또는 std::string을 가질 수 있는 Setting이라는 타입 별칭을 만드세요.

    마지막으로, Setting을 인자로 받아 어떤 타입을 가지고 있는지와 그 값을 다음 형식의 문자열로 반환하는 describeSetting 함수를 만드세요:

    • int의 경우: Integer: [value]
    • double의 경우: Double: [value]
    • string의 경우: String: [value]
  • DynamicStore.h: std::any를 사용하여 간단한 키-값 저장소를 만듭니다.

    어떤 타입의 값이라도 저장할 수 있는 DynamicStore 클래스를 정의하세요. 다음을 포함해야 합니다:

    • 문자열 키와 std::any 값을 받아 내부적으로 저장하는 set 메서드 (std::map을 사용하세요)
    • 키를 받아 std::any 값을 반환하는 get 메서드 (키가 존재하지 않으면 빈 std::any를 반환하세요)
    • 키가 존재하면 true를 반환하는 hasKey 메서드
  • main.cpp: 세 개의 입력을 읽습니다:
    1. 숫자일 수도 있는 문자열 (std::optional 테스트용)
    2. 타입 표시자: int, double, 또는 string
    3. 해당 타입에 대응하는 값

    세 가지 어휘 타입을 모두 시연하세요:

    첫째, 첫 번째 입력에 대해 parseValue 함수를 사용하세요. 값이 포함되어 있으면 Parsed: [value]를 출력하고, 비어 있으면 Parse failed를 출력하세요.

    둘째, 타입 표시자에 따라 Setting 변리언트(variant)를 생성하세요. 타입이 int이면 값을 정수로 저장하고, double이면 double로, string이면 문자열로 저장하세요. 그런 다음 describeSetting을 호출하고 결과를 출력하세요.

    셋째, DynamicStore를 생성하고 "status" 키 아래에 문자열 "config_loaded"를 저장한 다음, 이를 다시 가져와서 Status: [value]를 출력하세요. 값을 추출할 때는 std::any_cast<std::string>을 사용하세요.

예를 들어, 입력이 42, int, 100인 경우:

Parsed: 42
Integer: 100
Status: config_loaded

입력이 hello, double, 3.14인 경우:

Parse failed
Double: 3.14
Status: config_loaded

입력이 -15, string, username인 경우:

Parsed: -15
String: username
Status: config_loaded

필요한 곳에 적절한 헤더인 <optional>, <variant>, <any>, <string>, <map>을 포함하는 것을 잊지 마세요. 변리언트를 다룰 때는 std::holds_alternativestd::get을 사용하고, 옵셔널을 확인할 때는 has_value() 또는 직접적인 불리언 변환을 사용하세요.

치트 시트

현대적인 C++은 유연한 값을 처리하기 위한 세 가지 어휘 유형(vocabulary types)을 제공합니다:

std::optional<T>

존재할 수도 있고 존재하지 않을 수도 있는 값을 나타냅니다. <optional>을 포함하세요:

std::optional<int> findIndex(const std::string& str, char c) {
    for (size_t i = 0; i < str.size(); ++i) {
        if (str[i] == c) return i;
    }
    return std::nullopt;  // 값이 없음
}

auto result = findIndex("hello", 'l');
if (result.has_value()) {
    std::cout << *result;  // 값을 얻기 위해 역참조
}

std::variant<Types...>

지정된 유형 중 정확히 하나를 보유하는 타입 세이프(type-safe) 공용체입니다. <variant>를 포함하세요:

std::variant<int, double, std::string> data;

data = 42;
std::cout << std::get<int>(data);

data = "hello";
if (std::holds_alternative<std::string>(data)) {
    std::cout << std::get<std::string>(data);
}

std::any

런타임에 결정되는 모든 유형의 값을 보유할 수 있습니다. <any>를 포함하세요:

std::any value = 10;
std::cout << std::any_cast<int>(value);

value = std::string("text");
std::cout << std::any_cast<std::string>(value);

사용 시기: null 허용 값에는 std::optional을, 가능한 유형을 컴파일 타임에 알 수 있는 경우에는 std::variant를, 런타임 유형 유연성이 필요한 경우에는 std::any를 사용합니다.

직접 해보기

#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"

int main() {
    // 세 개의 입력 읽기
    std::string input1;  // 숫자일 수도 있는 문자열 (std::optional 테스트용)
    std::string typeIndicator;  // 타입 표시자: int, double, 또는 string
    std::string value;  // 해당 타입에 대응하는 값
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: Part 1 - parseValue를 사용하여 std::optional 테스트
    // input1과 함께 parseValue 함수 사용
    // 값이 포함되어 있으면 "Parsed: [value]"를 출력
    // 비어 있으면 "Parse failed"를 출력
    
    
    // TODO: Part 2 - Setting을 사용하여 std::variant 테스트
    // typeIndicator를 기반으로 Setting variant 생성
    // 타입이 "int"이면 값을 정수로 저장
    // 타입이 "double"이면 값을 double로 저장
    // 타입이 "string"이면 값을 문자열로 저장
    // 그 다음 describeSetting을 호출하고 결과 출력
    
    
    // TODO: Part 3 - DynamicStore를 사용하여 std::any 테스트
    // DynamicStore 생성
    // "status" 키 아래에 문자열 "config_loaded" 저장
    // 값을 검색하여 "Status: [value]" 출력
    // std::any_cast<std::string>을 사용하여 값 추출
    
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨