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

Modern C++ : std::any (17)

by snowoods 2025. 9. 30.

Modern C++

 

std::any

 

개요

std::any는 C++17부터 도입된 기능으로, 모든 타입의 값을 안전하게 저장하고 추출할 수 있는 타입 안전(type-safe) 컨테이너입니다. 특정 타입에 국한되지 않고 다양한 타입의 객체를 하나의 변수에 담아야 할 때 유용하게 사용됩니다.

 

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

  • C++17: std::any가 표준 라이브러리에 처음 도입되었습니다.

 

내용 설명

std::any는 내부에 어떤 타입의 값이든 저장할 수 있는 클래스 템플릿입니다. void*처럼 타입을 완전히 잃어버리는 것과 달리, std::any는 저장된 값의 타입을 기억하고 있어 타입 안전성을 보장합니다.

주요 멤버 함수는 다음과 같습니다.

  • any_cast<T>(): std::any 객체에 저장된 값을 특정 타입 T로 캐스팅하여 반환합니다. 만약 저장된 타입과 T가 일치하지 않으면 std::bad_any_cast 예외를 발생시킵니다.
  • has_value(): std::any 객체가 값을 가지고 있는지 여부를 true/false로 반환합니다.
  • type(): 저장된 값의 type_info를 반환합니다. typeid()와 함께 사용하여 저장된 타입을 확인할 수 있습니다.
  • reset(): 저장된 값을 파괴하고 std::any를 비웁니다.

 

예제 코드

#include <any>
#include <iostream>
#include <string>
#include <typeinfo>

// std::string_literals 네임스페이스를 사용하여 "..."s 형태의 문자열 리터럴을 사용합니다.
using namespace std::string_literals;

// std::any 객체에 저장된 값이 정수(int)인지 확인하는 함수
bool is_integer(const std::any &a)
{
    return a.type() == typeid(int);
}

// std::any 객체에 저장된 값이 문자열(std::string)인지 확인하는 함수
bool is_string(const std::any &a)
{
    return a.type() == typeid(std::string);
}

int main()
{
    // 1. std::any 객체 생성 및 값 할당
    auto value = std::any{42}; // int 타입의 42 저장
    std::cout << "Size of std::any: " << sizeof(value) << " bytes" << '\n';

    value = 42.0; // double 타입의 42.0 저장
    std::cout << "Size of std::any: " << sizeof(value) << " bytes" << '\n';

    value = "42"s; // std::string 타입의 "42" 저장
    std::cout << "Size of std::any: " << sizeof(value) << " bytes" << '\n';

    // 2. std::any_cast를 사용한 값 추출 (try-catch)
    try
    {
        // 현재 value는 std::string을 담고 있으므로 성공적으로 캐스팅됩니다.
        auto s = std::any_cast<std::string>(value);
        std::cout << "Successfully casted to string: " << s << '\n';
    }
    catch (const std::bad_any_cast &e)
    {
        std::cout << e.what() << '\n';
    }

    // 3. 타입 확인
    std::cout << "is_integer: " << std::boolalpha << is_integer(value) << '\n';
    std::cout << "is_string: " << std::boolalpha << is_string(value) << '\n';

    // 4. 값 존재 여부 확인
    std::cout << "has_value: " << std::boolalpha << value.has_value() << '\n';

    // 5. 포인터를 사용한 안전한 캐스팅 (예외를 발생시키지 않음)
    // 실패 시 nullptr을 반환합니다.
    int* int_ptr = std::any_cast<int>(&value);
    std::cout << "Casting to int*: " << (int_ptr ? "success" : "failed (returns nullptr)") << '\n';

    std::string* string_ptr = std::any_cast<std::string>(&value);
    std::cout << "Casting to string*: " << (string_ptr ? "success" : "failed (returns nullptr)") << '\n';
    if (string_ptr)
    {
        std::cout << "Value from pointer cast: " << *string_ptr << '\n';
    }

    // 6. 값 리셋
    value.reset();
    std::cout << "After reset, has_value: " << std::boolalpha << value.has_value() << '\n';

    return 0;
}

 

실행 결과

Size of std::any: 64 bytes
Size of std::any: 64 bytes
Size of std::any: 64 bytes
Successfully casted to string: 42
is_integer: false
is_string: true
has_value: true
Casting to int*: failed (returns nullptr)
Casting to string*: success
Value from pointer cast: 42
After reset, has_value: false

참고: sizeof(std::any)의 크기는 컴파일러와 아키텍처에 따라 다를 수 있습니다.

 

활용팁

  • std::variant와의 비교: 저장할 타입의 종류가 컴파일 타임에 알려진 제한된 집합이라면 std::variant를 사용하는 것이 더 효율적이고 안전합니다. std::any는 저장될 타입을 전혀 예측할 수 없는 경우에 사용하세요.
  • 예외 없는 캐스팅: std::any_cast는 잘못된 타입으로 캐스팅 시 예외를 던집니다. 예외 처리가 부담스럽다면 std::any_cast<T>(&any_object)처럼 포인터 형태로 캐스팅을 시도할 수 있습니다. 이 경우 캐스팅에 실패하면 nullptr를 반환하므로, 반환된 포인터를 확인하여 안전하게 값을 사용할 수 있습니다.
  • 성능 고려: std::any는 작은 객체는 내부에 직접 저장(Small Object Optimization)하지만, 일정 크기 이상의 객체는 동적 할당을 사용합니다. 이로 인해 힙 할당 및 가상 함수 호출에 따른 약간의 성능 저하가 발생할 수 있으므로, 성능이 매우 중요한 코드에서는 사용에 주의가 필요합니다.