はじめに
この記事は フリュー Advent Calendar 2025 の11日目の記事となります。
C++でリフレクションもどきを実装してみた【その2】
C++でもリフレクション使いたい!
でも言語仕様にないので、工夫して作ってみました。(検討はされているようですが)
過去記事はこちら
https://qiita.com/F-Ohata/items/c3f71e37792c5ad1ed9d
リフレクションとは
プログラム自身の構造を読み取ったり書き換えたりする技術のことです。
言語によりますが、できることをざっくり書き出します。
- クラス名一覧の取得 ← 今回ここ
- クラス名からインスタンス生成 ← 今回ここ
- メンバ変数一覧の取得
- メンバ変数名からメンバ変数の読み書き
- メンバメソッド一覧の取得 ← 今回ここ(new)
- メンバメソッド名から、メソッドの呼び出し ← 今回ここ(new)
- 各メンバ変数・メソッドのアクセス修飾子の取得
- クラスの継承関係の取得
など
C++で実装してみたコード
さっそくですが、コードの紹介です。
リフレクション用のコード
#include <iostream>
#include <string>
#include <functional>
#include <any>
#include <unordered_map>
// アセンブリ管理クラス
class Assembly
{
protected:
// クラス名とファクトリ関数のマップ
static std::unordered_map<std::string, std::function<void*()>> factoryMap;
// メソッド名とメソッドポインタのマップ
static std::unordered_map<std::string, std::any> methodMap;
public:
// メソッド登録
static void SetMethod(const std::string& methodName, const std::any& method)
{
methodMap[methodName] = method;
}
// インスタンス生成
static void* CreateInstance(const std::string& className)
{
auto it = factoryMap.find(className);
if (it != factoryMap.end())
{
return it->second();
}
return nullptr;
}
// メソッド呼び出し
template<typename R, typename C, typename... Args>
static R Invoke(const char* methodName, C* instance, Args&&... args)
{
auto& funcAny = methodMap[methodName];
auto funcPtr = std::any_cast<R(C::*)(Args...)>(funcAny);
return (instance->*funcPtr)(args...);
}
// クラス名を一覧表示
static void ShowClassNames()
{
for (auto& c : factoryMap)
{
std::cout << c.first << std::endl;
}
}
// メソッド名を一覧表示
static void ShowMethodNames()
{
for (auto& m : methodMap)
{
std::cout << m.first << std::endl;
}
}
};
// 静的メンバ変数の定義
std::unordered_map<std::string, std::function<void* ()>> Assembly::factoryMap;
std::unordered_map<std::string, std::any> Assembly::methodMap;
// クラス登録マクロ
#define RegisterClass(name) \
struct name##Factory : public Assembly { \
name##Factory() { \
Assembly::factoryMap[#name] = [](){ return new name; }; \
} \
} name##FactoryInstance;
使い方
// 自前で好きなインターフェースを実装する
class IUserInterface
{
public:
virtual void method1(int x) = 0;
virtual void method2(const std::string& str) = 0;
IUserInterface()
{
// メソッド登録(ださいけど)
Assembly::SetMethod("IUserInterface::method1", &IUserInterface::method1);
Assembly::SetMethod("IUserInterface::method2", &IUserInterface::method2);
}
};
// 実装クラス定義(ユーザー定義)
class Hoge : public IUserInterface
{
public:
void method1(int x) override
{
std::cout << "method1 called: " << x << std::endl;
}
void method2(const std::string& str) override
{
std::cout << "method2 called: " << str << std::endl;
}
};
// クラス登録
RegisterClass(Hoge);
int main()
{
// インスタンス生成
Hoge* hoge = (Hoge*)Assembly::CreateInstance("Hoge");
// メソッド呼び出し
Assembly::Invoke<void, IUserInterface, int>
("IUserInterface::method1", hoge, 1);
Assembly::Invoke<void, IUserInterface, const std::string&>
("IUserInterface::method2", hoge, "test");
// クラス名一覧表示
Assembly::ShowClassNames();
// メソッド名一覧表示
Assembly::ShowMethodNames();
return 0;
}
実行結果
method1 called: 1
method2 called: test
Hoge
IUserInterface::method1
IUserInterface::method2
この様にインスタンス生成とメソッド名からの呼び出しが出来ました。
感想
- 最初は黒魔術にまみれたコードを書いていて、引数の型を取得などが出来ましたが、メソッド呼び出しが思い通りにならず断念しました。
- std::functionを駆使しましたが、自動登録ができず「Assembly::SetMethod」を作らざるを得なかったです。くやしい。
- 前回と比べて、レベルアップしている感があります。C++の言語仕様が拡張されれば理想に近づく予感がします。
- やはり、C#のリフレクションは凄いです。