C++/Boost

[Boost] any / variant / optional

로파이 2022. 8. 20. 17:07

boost::any

C#, JAVA와 같이 모든 타입을 지칭할 수있는 타입을 제공한다.


필요 헤더

#include <boost/any.hpp>

 

모든 타입을 담을 수 있는 클래스

void example2()
{
    boost::any variable(std::string("Hello World!"));

    // if casting failed, may throw boost::bad_any_cast
    string s1 = boost::any_cast<string>(variable);

    // if casting failed, s2 is null
    string* s2 = boost::any_cast<string>(&variable);
}

값에 대한 any_cast는 타입이 맞지 않을 시 boost::bad_any_cast를 던진다.

포인터에 대한 any_cast는 null로 대체 된다.

 

내부에 같은 타입으로 포인터를 두고 있으며

생성할 때 ValueType을 담도록 동적 할당한 다음 값을 복사하여 placeholder가 가지고 있도록 한다.  

C++17에 정식으로 채택됐다.

RTTI 정보를 사용하므로 성능상 비용이 존재한다.

복사 생성, 값 생성, 복사 할당, 값 할당 시 동적 메모리 할당을 요구한다.

 

#include <boost/any.hpp>

using std::string;
using std::vector;
class BoostAny
{
public:
	static void example()
	{
		struct MyType
		{
			int val;
		};

		vector<boost::any> untyped_container;
		untyped_container.push_back(1);
		untyped_container.push_back(10.5f);
		untyped_container.push_back('w');
		untyped_container.push_back("value");
		untyped_container.push_back(MyType{ 10 });

		for (const auto& v : untyped_container)
		{
			if (auto p = boost::any_cast<int>(&v))
			{
				cout << "int: " << *p << endl;
			}
			else if (auto p = boost::any_cast<float>(&v))
			{
				cout << "float: " << *p << endl;
			}
			else if (auto p = boost::any_cast<char>(&v))
			{
				cout << "char: " << *p << endl;
			}
			else if (auto p = boost::any_cast<const char*>(&v))
			{
				cout << "string: " << *p << endl;
			}
			else if (auto p = boost::any_cast<MyType>(&v))
			{
				cout << "MyType: " << p->val << endl;
			}
		}
	}

 

boost::variant

#include <boost/variant.hpp>

boost::any의 경우 동적 할당과 RTTI 정보를 사용하므로 동적 코스트가 존재한다. 이에 boost::variant는 컨테이너에 여러 타입을 컴파일 시간에 결정해주고 동적 코스트 없이 사용할 수 있게 해준다.

 

boost::variant에 사용할 타입을 지정하여 컨테이너를 정의하면 여러 타입을 컨테이너에 담을 수 있다.

static void p_example()
{
    using my_var_t = boost::variant<int, const char*, string>;

    vector<my_var_t> some_values;
    some_values.push_back(10);
    some_values.push_back("Hello there!");
    some_values.push_back(std::string("Wow!"));

    string& s = boost::get<string>(some_values.back());
    s += "That is great!\n";
    cout << s;
}

 

특징

  • variant에는 boost::blank 타입이 있는데 빈 상태를 표현한다.
  • variant도 값 타입에 get을 한다면 boost::bad_get 예외를 던지나 포인터 타입에는 null을 반환하도록 한다.
  • variant 변수의 which()를 호출하면 정의한 타입의 인덱스를 반환한다.
  • C++17에 정식으로 도입되었다.
static void p_example2()
{
    using my_var_t = boost::variant<boost::blank, int, const char*, string>;

    my_var_t var;

    // variant가 소유한 형식의 인덱스를 반환
    assert(var.which() == 0);

    var = "Hello, dear reader";

    assert(var.which() != 0);

    my_var_t var2(0);

    // int가 아니라면 boost::bad_get 예외를 던진다.
    int s1 = boost::get<int>(var2);

    // int가 아니라면 null을 반환
    int* s2 = boost::get<int>(&var2);
}

 

방문자 패턴을 이용한 variant 활용

variant 타입을 이용하여 동적 타입 확인 없이 컨테이너를 순회하며 각 원소에 대해 타입에 따라 다른 작업을 요할 때 적용할 수 있다.

 

여러 타입의 필드를 갖는 db row를 타입에 맞춰 statement string을 만들어내는 예제

class BoostVariant
{
private:
	using cell_t = boost::variant<int, float, string, time_t>;
	using db_row_t = vector<cell_t>;

	template<size_t sz>
	struct statement_visitor : public boost::static_visitor<void> {

		size_t i = 0;
		std::stringstream& _ss;
		statement_visitor(std::stringstream& ss) : _ss(ss) {}

		void append_comma() {
			if (++i != sz)
				_ss << ", ";
		}

		void operator()(int value) {
			_ss << value;
			append_comma();
		}
		void operator()(float value) {
			_ss << value;
			append_comma();
		}
		void operator()(const std::string& s) {
			_ss << s;
			append_comma();
		}
		void operator()(const time_t& t) {
			struct tm tms;
			gmtime_s(&tms, &t);

			char buf[64];
			snprintf(buf, 64, "%4d-%02d-%02d %02d:%02d:%02d", tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday, tms.tm_hour, tms.tm_hour, tms.tm_sec);
			_ss << buf;
			append_comma();
		}
	};

	static std::string make_insert_statment(const db_row_t& row)
	{
		std::stringstream ss;
		statement_visitor<4> v(ss);
		ss << "INSERT INTO mytable VALUES(";
		for (int i = 0, cnt = row.size(); i < cnt; ++i) {
			boost::apply_visitor(v, row[i]);
		}
		ss << ")" << endl;

		return ss.str();
	}
public:
	static void example()
	{
		db_row_t row;
		row.push_back(0);
		row.push_back(1.0f);
		row.push_back(string("aaa"));
		row.push_back(time(NULL));

		string st = make_insert_statment(row);

		cout << "statement is " << st << endl;
	}
};

 

boost::optional

값이 존재하지 않은 상태를 표현. 동적 할당없이 값을 내부에 저장한다.

#include <boost/optional.hpp>

 

예시

#pragma once
#include "global.h"

#include <boost/optional.hpp>

class BoostOptional 
{
public:
	static void example()
	{
		using OptinalInt = boost::optional<int>;

		OptinalInt val;

		if (!val.has_value())
		{
			cout << "no value" << endl;
		}

		if (!val.is_initialized())
		{
			cout << "not initialied" << endl;
		}

		val.emplace(10);

		cout << "value : " << val.get() << " has value : " << val.has_value() << " initialized : " << val.is_initialized() << endl;

	}
};

'C++ > Boost' 카테고리의 다른 글

[Boost] 문자열 다루기  (0) 2022.08.21
[Boost] thread  (0) 2022.08.21
[Boost] program_options  (0) 2022.08.20
[Boost] 라이브러리 설치  (0) 2022.08.20