C++フレームワーク設計【2-より優雅なオブジェクト作成】


タスク#タスク#
前章では、以下のコードを使用してオブジェクトの作成を行いました.このコードには問題はありません.レイヤを追加するたびにels ifをここに追加すればいいです.しかし、もっと優雅な実現ができます.
LayerBase *LayerFactory(string classname)
{
	if (classname == "conv")
		return new LayerConv();
	else if (classname == "pooling")
		return new LayerPooling();
	else if (classname == "softmax")
		return new LayerSoftmax();
	else
		return nullptr;
}

構想
計画はmapマッピングテーブルを構築し、keyはクラス名またはIDであり、valueは戻り可能なオブジェクトの関数ポインタである.これで上の関数がこのようになります.
// LayerFactory.h
#include
using std::function;

class LayerFactory
{
public:
	static LayerBase *Create(string str){
		function <LayerBase*()> fun = s_CreateMap[str];
		if (nullptr == fun)
			return nullptr;
		return fun();
	}

	static void RegisterCreater(string classname, function<LayerBase*()> creater)
	{
		s_CreateMap[classname] = creater;
	}
private:
	static map<string, function<LayerBase*()>> s_CreateMap;
};

// LayerFactory.cpp
#include"LayerFactory.h"
LayerBase *LayerFactoryMap(string classname)
{
	return LayerFactory::Create(classname);
}
s_CreateMapはこのマッピングテーブルであり,最も直接的な考え方はクラスごとのコンストラクション関数をs_CreateMapに与えることであるが,C++はクラスを指すコンストラクション関数を取得できない関数ポインタである.そこで考え方を変えて、別の関数(例えばfwrap)でコンストラクション関数を封印し、fwrapのアドレスをmapテーブルに付与すればよい.layerクラスごとにこのようなfwrap関数を定義すればよい.メンバー関数は関数ポインタを得ることができるが、呼び出す際に具体的なオブジェクト(パラメータリストにthisポインタを伝える必要があるため)が必要である.これで呼び出しが面倒になります.したがって,最後にグローバル関数またはクラスの静的関数のみが要求に合致する.Convレイヤの例として、以下の実装が得られる.
class AUTO_FACTORY_Conv 
{ 
public: 
	AUTO_FACTORY_Conv(){ LayerFactory::RegisterCreater("conv", AUTO_FACTORY_Conv::CreateLayer); };
	static LayerBase* CreateLayer() { return new LayerConv(); } 
}; 

ここに登録文がAUTO_に置かれているのがわかりますFACTORY_Convクラスのコンストラクション関数にあります.これは、オブジェクトを宣言するだけで、文字列とオブジェクトコンストラクタのマッピング関係を自動的に登録できるようにするためです.例:
static AUTO_FACTORY_Conv g_obj_for_register_conv;

そのCreateLayer関数は、上述したfwrapである.
このコードは非常にパターン化されていることがわかり,c++の慣例に従ってマクロでまとめる.
#define REGISTER_LAYER_CREATE(idname,classname) \
class AUTO_FACTORY_##idname \
{ \
public: \
	AUTO_FACTORY_##idname(){ LayerFactory::RegisterCreater(#idname, AUTO_FACTORY_##idname::CreateLayer); }; \
	static LayerBase* CreateLayer() { return new classname(); } \
}; \
static AUTO_FACTORY_##idname class_creater_register_##idname;

最終的な完全なコードは次のとおりです.
// LayerFactory.h
#include
using std::function;
LayerBase *LayerFactory(string classname);

class LayerFactory
{
public:
	static LayerBase *Create(string str){
		function <LayerBase*()> fun = s_CreateMap[str];
		if (nullptr == fun)
			return nullptr;
		return fun();
	}

	static void RegisterCreater(string classname, function<LayerBase*()> creater)
	{
		s_CreateMap[classname] = creater;
	}
private:
	static map<string, function<LayerBase*()>> s_CreateMap;
};


#define REGISTER_LAYER_CREATE(idname,classname) \
class AUTO_FACTORY_##idname \
{ \
public: \
	AUTO_FACTORY_##idname(){ LayerFactory::RegisterCreater(#idname, AUTO_FACTORY_##idname::CreateLayer); }; \
	static LayerBase* CreateLayer() { return new classname(); } \
}; \
static AUTO_FACTORY_##idname class_creater_register_##idname;

// LayerFactory.cpp
#include"layerFactory.h"

map<string, function<LayerBase*()>> LayerFactory::s_CreateMap;

// register
REGISTER_LAYER_CREATE(conv,LayerConv)
REGISTER_LAYER_CREATE(pooling, LayerPooling)
REGISTER_LAYER_CREATE(softmax, LayerSoftmax)

LayerBase *LayerFactoryMap(string classname)
{
	return LayerFactory::Create(classname);
}

小結
  • は1周して、コードの量は減らしていないようで、前のif elseはよくありませんか.より複雑なニーズを考慮してこそ、コードを自動的に生成する必要がある(プロファイルに基づいて重複コードを一括生成する).マクロを挿入すると、関数内の論理を修正するよりも便利になります.
  • REGISTER_LAYER_CREATEマクロは最終的にlayerFactoryに適用する.cpp中.すなわち、登録に使用されるクラス宣言は、このcppファイルのみが表示され、定義された登録を支援するクラスもstaticに設定されます.これらは、オブジェクトコンストラクタの詳細をこのコンパイルユニットにカプセル化するためです.
  • 工場モデルの定義によると、単純な工場(設計モデルにも属さない、パッケージを適用した思想)に属するため、設計モデルは多ければ多いほど複雑であればあるほど良いのではなく、業務に適した
  • である.
    referecne
    1.工場モデル2.このようなmapを構築する方法はあるブログから学んだが、出典を覚えていないので後で補う.