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<functional>
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<functional>
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不挺好的嗎。考慮到更復雜的需求時才能體現他的好處,比如我們有自動生成代碼的需求(根據配置文件,批量生成重複代碼)。那麼插一句宏會比修改函數體內的邏輯來的更方便。
  2. REGISTER_LAYER_CREATE宏最終應用在layerFactory.cpp中。即用於註冊的類聲明只有這個cpp文件可見,且定義的幫助註冊的類也是置爲static的,這些都是爲了把對象構建器的細節封裝到這個編譯單元內。
  3. 根據工廠模式的定義,屬於簡單工廠(甚至不屬於設計模式,就是應用了封裝的思想)。所以設計模式不是用的越多越複雜越好,適合業務的纔是最好的

referecne

1.工廠模式
2.這種構建map的方式是從某篇博客學來的,但不記得出處回頭補上

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章