はじめに
この記事は フリュー Advent Calendar 2022 の14日目の記事となります。
C++でリフレクションもどきを実装してみた。
C++でもリフレクション使いたい!
でも言語仕様にないので、工夫して作ってみました。(検討はされているようですが)
といっても文字列からクラスのインスタンスを生成する部分だけになります。
リフレクションとは
プログラム自身の構造を読み取ったり書き換えたりする技術のことです。
言語によりますが、できることをざっくり書き出します。
- クラス名一覧の取得 ← 今回ここ
- クラス名からインスタンス生成 ← 今回ここ
- メンバ変数一覧の取得
- メンバ変数名からメンバ変数の読み書き
- メンバメソッド一覧の取得
- メンバメソッド名から、メソッドの呼び出し
- 各メンバ変数・メソッドのアクセス修飾子の取得
- クラスの継承関係の取得
など
C++で実装してみたコード
さっそくですが、コードの紹介です。
リフレクション用のコード
#include <string>
#include <vector>
#include <exception>
#include <functional>
#include <iostream>
class Assembly
{
public:
// リフレクション用のインターフェース
class IReflection
{
public:
std::function<const std::string&()> GetTypeName;
std::function<void*()> CreateInstance;
// 所有権の移動によりコピーの無駄を無くしておく
IReflection(
std::function<const std::string&()>&& getTypeName,
std::function<void*()>&& createInstance) :
GetTypeName(getTypeName),
CreateInstance(createInstance)
{
}
};
private:
// リフレクション用のインターフェースの集合
static std::vector<Assembly::IReflection> assemblies;
public:
// クラス名を一覧表示
static void ShowClassNames()
{
for (IReflection& a : assemblies)
{
std::cout << a.GetTypeName() << std::endl;
}
}
// ファクトリ(インスタンス生成)用のメソッド
static void* CreateInstance(const std::string& typeName)
{
for (IReflection& a : assemblies)
{
if (a.GetTypeName() == typeName)
{
return a.CreateInstance();
}
}
throw std::exception(("Assembly::CreateInstance NotFound typeName=" + typeName).c_str());
}
// リフレクションを楽に実装するためのラッパークラス
template< class T > class Reflection
{
private:
const std::string typeName = typeid(T).name();
public:
Reflection()
{
Assembly::assemblies.emplace_back( std::bind(&Reflection<T>::GetTypeName, this), CreateInstance );
}
const std::string& GetTypeName() const
{
return typeName;
}
static void* CreateInstance() { return new T(); }
};
};
// リフレクションクラスの集合の実体(cppファイルに記載してください)
std::vector<Assembly::IReflection> Assembly::assemblies;
// リフレクション登録のためのマクロ
// ※名前空間を設定しているのは、変数名の重複を避けるため。
#define SetReflection(className) namespace className##Reflection \
{ \
static Assembly::Reflection< className > reflection; \
}
使い方
// 自前で好きなクラスを実装する
class Hoge
{
private:
int num = 30;
public:
void print() const
{
std::cout << num << std::endl;
}
};
// リフレクションしたいクラスを設定
SetReflection(Hoge);
/* このマクロを展開すると以下の様になります。
namespace HogeReflection
{
static Assembly::Reflection< Hoge > reflection;
}
*/
void main()
{
// クラス名一覧表示
Assembly::ShowClassNames();
// クラス名からインスタンスを生成 ※指定する文字列はtypeidのnameに依存
std::unique_ptr<Hoge> hoge( (Hoge*)Assembly::CreateInstance("class Hoge") );
// メソッド実行
hoge->print();
}
実行結果
class Hoge
30
この様にクラス名の一覧取得と、インスタンス生成からのメソッド呼び出しが出来ました。
感想
- リフレクションは、言語レベルのサポートが無いとキツイです。C#は秀逸です。
- 自動登録ができず「SetReflection」を作らざるを得なかったです。くやしい。
- これ以上作りこむ場合は、C++のコードジェネレータを作成する方が良いと思います。
※ビルド前に実行して、ソースを出力し、それをビルドする形