日本語
std::variant & std::visit

std::variant & std::visit

C++17で追加されたvariantとvisitに関する内容のまとめ

その他の情報

[関連ドキュメント](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0088r3.html

)

上記のリンクには、C++17から追加されたVariantとその追加理由について記載されています。簡単に言えば、std::optionalとして追加されるべきだった機能が、Boostで長期間使用されていたため、「これ以上引き延ばさずに、早く実装してしまおう」という議論がなされたということです。

内部構造

1. Variant

  • データバッファ テンプレート引数として与えられた型の中で、最もサイズが大きい型を格納できる十分なサイズのメモリ領域であり、概ねUnionとして実装されている

  • 識別子 現在、データバッファにどの型のオブジェクトが格納されているかを示すインデックスまたは識別子であり、この識別子を使用することで、std::variantは型安全性を保証できる

十分なサイズのメモリ領域を持つことにより、variant型は以下のような動作が可能となる。


	struct Tracer
	{
		std::string name;

		Tracer(const std::string& n) : name(n)
		{
			std::cout << "  [+] '" << name << "' Tracer created (Constructor)\n";
		}

		// Copy Constructor
		Tracer(const Tracer& other) : name(other.name)
		{
			std::cout << "  [*] '" << name << "' Tracer copy-constructed (Copy Constructor)\n";
		}

		~Tracer()
		{
			std::cout << "  [-] '" << name << "' Tracer destroyed (Destructor)\n";
		}
	};

	// 1. Initialize the variant with a Tracer type.
	std::cout << "1. Initializing variant with Tracer(\"Apple\").\n";
	std::variant<int, Tracer> var = Tracer("Apple");
	std::cout << "   Variant now holds Tracer(\"Apple\").\n\n";

	// 2. Assign a value of a different type (int).
	std::cout << "2. Assigning integer 100 to the variant.\n";
	var = 100;
	std::cout << "   Variant now holds integer 100.\n\n";

	// 3. Assign another Tracer type value again.
	std::cout << "3. Assigning Tracer(\"Banana\") to the variant.\n";
	var = Tracer("Banana");
	std::cout << "   Variant now holds Tracer(\"Banana\").\n\n";

	std::cout << "4. main function is about to end.\n";
	return 0;

このようなコードを実行すると、以下のような結果が得られる。

1. Initializing variant with Tracer("Apple").
  [+] 'Apple' Tracer created (Constructor)
  [*] 'Apple' Tracer copy-constructed (Copy Constructor)
  [-] 'Apple' Tracer destroyed (Destructor)
   Variant now holds Tracer("Apple").

2. Assigning integer 100 to the variant.
  [-] 'Apple' Tracer destroyed (Destructor)
   Variant now holds integer 100.

3. Assigning Tracer("Banana") to the variant.
  [+] 'Banana' Tracer created (Constructor)
  [*] 'Banana' Tracer copy-constructed (Copy Constructor)
  [-] 'Banana' Tracer destroyed (Destructor)
   Variant now holds Tracer("Banana").

4. main function is about to end.
  [-] 'Banana' Tracer destroyed (Destructor)

既存の値に対するデストラクタが先に呼び出され、スタック上に一時オブジェクトを生成した後、コピー生成された値を自身のメモリバッファに取り込む順序を確認できる。

したがって、最大サイズのクラスを格納するスペースを内部的にユニオンとして保持しておけば、variant型にどのような値が入ってきても問題なく使用できるようにすることができる!

2. Visit

これはライブラリによって異なるが、

  • 関数ポインタテーブル

  • Switch文

大きく2つの形態に分類されるという。実際、実装方式やオーバーヘッド、コンパイラの最適化に若干の違いがあるだけで、基本的には識別子を見て、アクティブ化されたTypeに応じて適切な関数を呼び出せるように実装されていると考えてよいだろう。

댓글 작성

게시글에 대한 의견을 남겨 주세요.

댓글 0