C++でAny型もどきを実装してみる

OpenSSLでWebAPIを扱って・・・と思っていたときに出てきた問題。

WebAPIの戻り値はだいたいがJSONなので、JSONを解析する必要性がありまして、それはまだいいのですが、次の問題がやっかい。

解析した結果は一応std::mapで示すことはできるのですが、そのときに何でも入るクラスとしてany型を使って

bool ParseJSON(std::map<std::string,any> &retval,const std::string &json);

のような宣言が必要になります。まあ、このときにanyがなり得る対象は

  • std::string型
  • map<std::string,any>型

のどちらかのはずなのですが・・・。

入れ子構造が可能なのでそれを実現するためには管理クラスが必要で・・・。ということでany型の登場となりました。

Boostライブラリにすでに実装されたものがある

ということで、単に使いたいだけならBoostを使ってください、で終了です。

さすがにC++の様々なライブラリを実装しているだけありますね。すごいです。

それだとつまらないので自前で実装してみた

一応実装してみた例のコードです。anyではなくanytypeとして組んでいます。

#ifndef __anytype_h__
#define __anytype_h__
#include <typeinfo>
//anytypeによる例外
class anytype_nullreference_exception : public std::exception{
public:
	anytype_nullreference_exception(const char * _Message = "anytype null reference") : std::exception(_Message){ }
	anytype_nullreference_exception(const anytype_nullreference_exception &object) : std::exception(object){ }
	virtual ~anytype_nullreference_exception() { }
};
class anytype_badcast_exception : public std::bad_cast{
public:
	anytype_badcast_exception(const char * _Message = "anytype bad cast") : std::bad_cast(_Message){ }
	anytype_badcast_exception(const anytype_badcast_exception &object) : std::bad_cast(object){ }
	virtual ~anytype_badcast_exception() { }
};
//何でも入るクラス
class anytype{
protected:
	//内部値基本クラス
	class anybase{
	public:
		virtual ~anybase(){ } //デストラクタ
		virtual const std::type_info & type(void) const = 0; //型情報の取得
		virtual anybase *clone(void) const = 0; //複製を行う
	};
	//内部値保持クラス
	template <typename valtype> class anyitem : public anybase{
	public:
		anyitem(const valtype &value) : m_item(value) { } //コンストラクタ
		virtual ~anyitem() { } //デストラクタ
		virtual const std::type_info & type(void) const { return typeid(valtype); } //型情報の取得
		virtual anybase *clone(void) const { return new anyitem(m_item); } //複製を行う
		valtype & value(void) { return m_item; } //アイテムを取得する
	private:
		valtype m_item; //本体アイテム
	};
public:
	//コンストラクタ
	anytype() : m_any(NULL) {  }
	//コピーコンストラクタ
	anytype(const anytype &value) : m_any(NULL)
	{
		if(value.m_any != NULL){
			m_any = value.m_any->clone();
		}
	}
	//コンストラクタ(値付き)
	template <typename valtype> anytype(const valtype &value) : m_any(new anyitem<valtype>(value)) { }
	//コピーオペレータ
	anytype & operator = (const anytype &value)
	{
		if(this != &value){
			if(m_any != NULL){ delete m_any; m_any = NULL; }
			if(value.m_any != NULL){ m_any = value.m_any->clone(); }
		}
		return *this;
	}
	template <typename valtype> anytype & operator = (const valtype &value) { set(value); return *this; }
	//値の設定
	template <typename valtype> void set(const valtype &value)
	{
		if(m_any != NULL){ delete m_any; }
		m_any = new anyitem<valtype>(value);
	}
	//値の削除
	void clear(void) { if(m_any != NULL){ delete m_any; m_any = NULL; } }
	//値の入れ替え
	void swap(anytype &value)
	{
		anybase *any = m_any; m_any = value.m_any; value.m_any = any;
	}
	//値の取得
	template <typename valtype> valtype & cast(void) const
	{
		if(m_any == NULL) throw anytype_nullreference_exception();
		if(m_any->type() != typeid(valtype)) throw anytype_badcast_exception();
		return static_cast<anyitem<valtype> *>(m_any)->value();
	}
	template <typename valtype> valtype & unsafecast(void) const
	{
		if(m_any == NULL) throw anytype_nullreference_exception();
		return static_cast<anyitem<valtype> *>(m_any)->value();
	}
	//型情報の取得
	const std::type_info & type(void) const
	{
		return m_any != NULL ? m_any->type() : typeid(void);
	}
	const char *name(void) const
	{
		return type().name();
	}
	//値を保持しているかどうか
	bool empty(void) const
	{
		return m_any == NULL;
	}
	
private:
	anybase *m_any; //アイテム本体
};
#endif //__anytype_h__

よく使うテクニックとして

  • 基本型はテンプレートではないただのインターフェイスクラス
  • 継承をするときにテンプレートを指定した型を使い基本型にキャストすることで「何でも」の部分を型状態を保持して単一の型にしてしまう

というものがあります。これ以外にもスマートポインタのコードでも出てきたりします。

あとはtypeid(valtype)ですか。指定した型情報をtype_info構造体で取得するC++特有の予約語です。

type_info構造体(および、それに関連する例外)はtypeinfoにあるので読み込んでおきましょう。

検証するときは==を使うかnameメンバ関数より型名を取得してstrcmpなどで照合します。

だいたいはこんな感じで実装します。

結局、anyに対応する型をある程度限定できないと使えないのは当たり前か

これを使うときにはこんな感じです。

#include <iostream>
#include <string>
#include <vector>
#include "anytype.h"
using namespace std;
int main(void)
{
	anytype v1,v2,v3,v4; vector<int>::iterator it,itend;
	v1 = 10; v2 = 5.75f; v3 = string("50"); v4.set(vector<int>());
	v4.cast<vector<int> >().push_back(20);
	v4.cast<vector<int> >().push_back(30);
	cout << v1.name() << ":" << v1.cast<int>() << endl;
	cout << v2.name() << ":" << v2.cast<float>() << endl;
	cout << v3.name() << ":" << v3.cast<string>() << endl;
	cout << v4.name() << ":";
	for(it = v4.cast<vector<int> >().begin(),itend = v4.cast<vector<int> >().end();it != itend;++it){ cout << *it << " "; }
	cout << endl;
	return 0;
}
&#91;/cpp&#93;
<p>出力するときに入っている可能性のある型を指定しないと値が出てきません。</p>
<p>また、型から出すときにダウンキャスト等もうまくできないのが悲しいです。</p>
<p>整数型が入っているときに浮動小数で取得する、ということもできません。</p>
<p>ちなみに、これをやってみるとおもしろい結果になると思います。anytype内にintが入っている時にunsafecastにfloatを指定して取得するとどうなるか、というやつです。</p>
<br>
<p>まあ、ある程度中に入っている型が限定されるならtype()やname()で照合を行って取得することが可能なので</p>
<p>その意味では使える余地はあります。たとえば</p>

typedef std::vector<anytype> anyarray;
typedef std::list<anytype> anylist;

のようにすると「何でも入る配列」や「何でも入るリスト」が一応再現できることになります。

改良版は次の記事に

上に書いた「整数型が入っているときに浮動小数で取得する、ができない」という現象を修正してみたいと思います。

が、これがまた面倒で、テンプレートの特殊化やら変換時に整数型最大の型を使う、などといった作業が必要になります。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL