한 변수에 임의의 타입의 값을 담아야할 때 어떻게 해야 할까요? 당장 생각나는건 보이드 포인터(void*)와 타입 캐스팅입니다. C++에서는 reinterpret_cast를 써야겠죠

그런데 이런식으로 쓰려면 기존 void*에 새 값을 할당할 때 내부에 값이 있는지 확인하고, 이전에 할당된 값을 잘 소멸시켜줘야 합니다. 값을 꺼내와서 캐스팅해 사용할 때도 타입을 잘 맞춰줘야 합니다.

class any; (since C++17)

The class any describes a type-safe container for single values of any type.

1) An object of class any stores an instance of any type that satisfies the constructor requirements or is empty, and this is referred to as the state of the class any object. The stored instance is called the contained object. Two states are equivalent if they are either both empty or if both are not empty and if the contained objects are equivalent.
2) The non-member any_cast functions provide type-safe access to the contained object.

- cppreference

C++17부터는 std::any가 추가됐습니다. 헤더는 <any>입니다. 이전 방식보다 여러가지 이점이 있습니다. 대충 type-safe한 void* 객체라고 생각하면 될 듯 합니다.

 

#include <any>
#include <iostream>

struct pt {
	int x, y;
};

int main() {
	std::any val;
	val = 1234;
	val = std::string("hi");
	val = pt{ 1, 2 };

	std::cout << std::any_cast<pt>(val).x << '\n';	// 1

	return 0;
}

이런식으로 아무 값이나 담을 수 있는데 이때 새 값을 담기 전에 any에 값이 이미 들어가 있었다면 내부적으로 알아서 delete를 합니다. 참 편하죠

msvc any 소스

MSVC STL 소스를 보면 후술할 reset 메소드로 내부에 저장된 값을 터트린 다음에 copy를 하는 것을 확인할 수 있습니다.

any에 있는 값을 꺼내올 때는 any_cast로 캐스팅해서 꺼내와야 합니다. 맞지 않는 타입으로 캐스팅한 경우 런타임에 bad_any_cast가 throw 됩니다.

 

std::cout << std::boolalpha;

std::any val;
std::cout << val.has_value() << '\n';	// false

val = pt{ 1, 2 };
std::cout << val.has_value() << '\n';	// true

std::cout << val.type().name() << '\n';

has_value() 메소드로 현재 any에 값이 할당되었는지 아닌지 확인할 수 있고, type()은 현재 any에 들어있는 값의 std::type_info를 반환합니다.

할당되지 않은 any의 경우 typeid(void)의 type_info 객체를 반환합니다.

참고로 std::type_info::name()은 implementation defined name을 출력합니다.

 

#include <any>
#include <iostream>
#include <vector>

struct pt {
	int x, y;
	pt(int _x, int _y) : x(_x), y(_y) {}
};

int main() {
	std::any val;
	val.emplace<pt>(1, 2);	//args
	auto p = std::any_cast<pt>(val);

	val.emplace<std::vector<int>>({ 1, 2, 3 });	//std::initializer_list
	auto v = std::any_cast<std::vector<int>>(val);

	return 0;
}

emplace 메소드로 생성자 argument들을 넘겨서 any에다가 바로 넣을 수 있습니다. std::initializer_list 사용도 역시 가능합니다.

 

std::cout << std::boolalpha;

std::any val;
val = 1;
std::cout << val.has_value() << '\n';	// true

val.reset();
std::cout << val.has_value() << '\n';	// false

reset 메소드로 명시적으로 any 안에 들어있는 값을 없앨 수 있습니다.

 

std::any v_any = std::make_any<std::vector<int>>({1,2,3,4});
auto v = std::any_cast<std::vector<int>>(v_any);
	
for (auto& c : v)
	std::cout << c << '\n';

std::make_any 함수를 사용해 any 인스턴스를 만들 수 있습니다. emplace처럼 args를 넘기거나 std::initializer_list를 사용할 수 있습니다.

자 이렇게 대충 any 사용법을 정리를 해봤는데요, reinterpret_cast도 거의 써본 적 없는 저로서는 any 역시 사용할 일이 거의 없을 듯 합니다.

 

가슴에 손을 한번 얹고 생각해봅시다.

  • 진짜 C++에서 이 짓을 해야 하는가?
    • YES -> 설계가 잘못된 건 아닐까? 인터페이스 만들어서 상속하는 식으로 하는게 낫지 않을까?
    • NO -> 그냥 파이썬 쓰면 안될까?
반응형