C++17 표준 라이브러리의 std::any 소개
C++17 표준 라이브러리에 추가된 std::any를 소개합니다.
다음은 6월에 출간된 "핵심 C++ 표준 라이브러리"의 부록 A의 일부입니다. 원래는 블로그 글을 먼저 쓰고 그걸 다듬어서 부록으로 수록하려 했는데(stringview 소개글과 optional 소개글이 그런 시도의 결과입니다) 책이 먼저 나와버렸습니다.
어쨌거나, 핵심 C++ 표준 라이브러리 번역서 출간과 연계해서 이 블로그에서 C++17 표준 라이브러리를 소개한다는 애초의 계획을 실행하기 위해 부록의 나머지 부분을 이곳에 차츰 올리도록 하겠습니다. 첫 타자는 std::any
입니다. 블로그 말투가 아니라 단행본 문체라 좀 어색할 수도 있지만, 그냥 그대로 갑니다!
아무 형식이나 담을 수 있는 std::any
std::any
형식의 객체에는 말 그대로 ‘아무(any)’ 형식의 값을 담을 수 있다. 단, 복사가 가능한(copyable) 형식이어야 한다. 필요한 헤더는 <any>
이다.
struct Position { double x, y, z; };
void f()
{
any a = std::string("C++ 문자열");
a = 123; // 새 값이 배정되기 전에 기존 값(string 객체)이
// 파괴된다(소멸자 호출 및 메모리 해제).
a = Position{1, 2, 3}; // 복사할 수 있는 형식이면 어떤 형식도 가능.
}
이런 식으로 한 변수에 임의의 형식의 값을 담아야 할 때 흔히 쓰이는 수단은 무형식 포인터(typeless pointer) void*
와 reinterpret_cast
(또는 C 스타일 캐스팅)이다. 그러나 그 둘만으로는 형식 안전성을 보장하기 힘들다. 형식 안전성을 위해서는 any 객체에 새 값이 배정되기 전에 기존 값이 제대로 파괴되어야 하는데, void*
만으로는 그것을 보장하기 힘들다. 그러나 앞의 예제의 주석에서 보듯이 any
는 객체의 적절한 파괴(소멸)를 보장한다. 이런 측면 때문에 std::any
를 “형식에 안전한(typesafe) void*
”라고 표현하기도 한다.
any
객체에 담긴 값을 꺼낼 때는 std::any_cast
라는 함수를 사용한다.
any a = string("C++ 문자열");
cout << any_cast<string>(a) << endl // C 문자열
int i = any_cast<int>(a); // 형식이 일치하지 않음:
// bad_any_cast 예외 발생.
위의 예처럼 any
객체 자체를 인수로 받는 any_cast
는 만일 요청된 형식과 자신에 담긴 자료의 형식이 일치하지 않으면 std::bad_any_cast
형식의 예외를 던진다. 이러한 형식 점검은 std::any
를 “형식에 안전한 void*
”라고 부르는 또 다른 이유이다.
형식이 일치하지 않을 때 예외가 발생하는 것을 원하지 않는다면, 다음처럼 포인터를 받고 포인터를 돌려주는 버전의 any_cast
를 호출하면 된다. 이 버전은 형식이 일치하지 않으면 널 포인터(nullptr
)를 돌려준다.
optional
처럼 any
에도 빈 객체를 생성하는 기본 생성자가 있으며, 멤버 함수 has_value
, emplace
, reset
도 있다. any
의 멤버 함수 type
은 any
객체에 담긴 자료의 형식을 나타내는 type_info
객체(에 대한 참조)를 돌려준다. 빈 any
객체의 경우 그 type_info
객체는 typeid(void)
에 해당한다.
any a; // 빈 객체
cout << a.has_value() << endl; // false
a.emplace<string>("Hello, world!"); // 제자리 생성.
cout << a.type().name << endl; // 구체적인 형식 이름은
// 컴파일러마다 다를 수 있음.
opt2.reset(); // 이제 has_value()는 false.