自らを責める地獄のアドカレ一日目です。
はじめに
C++で複数の型を一つのポインタで同時に扱いたいと思ったことはありませんか?私はありました。
設計を変えて型それぞれに別のポインタを割り当てたほうがいいとは思いますが、どうしても複数の型を一つの変数で扱いたいときのための備忘録のようなものです。
方法
- std::anyを使用する
- unionを使用する
std::anyを使用する
std::any
std::anyとは様々な型の値を動的に保持できる標準ライブラリのクラスです。
一般的なユースケースではこちらで十分です。
具体例
std::any value;
value = int(5);
try {
int i = std::any_cast<int>(value);
printf("%d\n", i);
} catch (std::bad_any_cast& error) {
// キャストできなかった際のエラー
}
Unionを使用する
Union
unionは複数の型をstructのように扱う方法です。
structとは違い、すべての変数は同じメモリで管理され、メモリのbyteを共有しています。
std::anyと比べる
std::anyと違い、内部の変数の寿命を完全に制御することができ、また参照の扱いもstd::anyと比べ複雑ではありません。
しかし、std::anyを使用した方が安全な場合も多いと思います。
unionを使用するのは高度な制御を要求された場合のみにした方が良いかと思います。
Unionでint型とchar型を同時に扱う例
union int_char{
int i;
char c;
};
これで、intとcharを同時に扱うint_char型を定義できました。
このあとは、普段通りにint_char型を使うことができます。扱いとしては、int型とchar型を合わせたようなもので、int_char+intのような演算をすることもできます。
int_char型の操作
コメントでご指摘いただいたようにintとcharのunionでintの値を操作しcharの値を表示するような方法はあまり推奨されません。int型のbyteの並びは環境依存です。あくまで、intを操作したらcharも変わっていることを表現するためのコードです。
int_char x;
x.i = 0;
x.c = 'c';
printf("%c %d\n", x.c, x.i); // c 99
x.i++;
printf("%c %d\n", x.c, x.i); // d 100
このように、int型としてもchar型としても扱えます。99からint型の値をインクリメントすると、char型の値はintと同じ値を参照しているので、100のcharに対応する'd'が出力されます。
このコードでx.i = 0;
を入れているのは、char型が1byteに対して、int型が4byteなので、int型に合わせ変数xのために、4byte確保されますが、このままx.c = 'c';
を代入すると、1byte目以外のbyteは初期化されない状態なので、int型に0を入れることで、4byte分初期化しています。
この、最大サイズの型にあわせて初期化をしなければならないのが、Unionの落とし穴かと思います。
具体例
実体をstd::byteでもつValueクラスのサンプル
あくまでサンプルです。int型,double型等の数値型以外からの代入は想定していませんし、安全性もあまり考慮していません。
class Value {
size_t size;
byte* data;
public:
// メンバ変数dataにT型から代入する
template <class T>
void set(T val) {
union {
T val;
byte b[sizeof(T)];
} union_byte;
cout << sizeof(T) << endl;
union_byte.val = val;
data = union_byte.b;
size = sizeof(union_byte);
}
};
まとめ
- c++で一つの変数で複数の型を扱うためには基本的にstd::anyを使うとよい。
- より複雑な管理を行いたいときはunionを使う。
- unionを扱うときは一番大きなサイズの型で初期化を行う。