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를 선택하세요.
챌린지
쉬움현대적인 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]
- int의 경우:
DynamicStore.h:std::any를 사용하여 간단한 키-값 저장소를 만듭니다.어떤 타입의 값이라도 저장할 수 있는
DynamicStore클래스를 정의하세요. 다음을 포함해야 합니다:- 문자열 키와
std::any값을 받아 내부적으로 저장하는set메서드 (std::map을 사용하세요) - 키를 받아
std::any값을 반환하는get메서드 (키가 존재하지 않으면 빈std::any를 반환하세요) - 키가 존재하면 true를 반환하는
hasKey메서드
- 문자열 키와
main.cpp: 세 개의 입력을 읽습니다:- 숫자일 수도 있는 문자열 (
std::optional테스트용) - 타입 표시자:
int,double, 또는string - 해당 타입에 대응하는 값
세 가지 어휘 타입을 모두 시연하세요:
첫째, 첫 번째 입력에 대해
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_alternative와 std::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;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.