在caffe里总共使用了3次工厂模式,一次是开始训练时的train,test函数中使用的,第二次是layer注册的使用,第三次是调用solver时使用的。这里我们来介绍下layer注册时的工厂模式。使用工厂模式主要是对layer层可以很好的管理,在添加新的层时,不需要关注其他的代码,只需要添加自己添加的层的代码,方便代码扩展。但是也有缺点,就是在阅读代码时,让很多新手读不懂。里面使用了很多c++高级语言特性,这里稍微介绍下。
1.什么是工厂模式
工厂模式简单来说就是,封装通用的东西,不同的东西进入工厂,可以生产不同的产品。例如,有一个基类product,两个子类shoes,clothes,都继承product。现在需要一个函数,要求返回product的实例,同时要求:传入shoes返回shoes类对象,传入clothes返回clothes对象。这样我们使用if就可以了:
class Product{};
class shoes:Product{};
class clothes:Product{};
Product* make(string what){
if(what=="shoes") return new shoes();
if(what=="clothes") return new clothes();
return null;
}
这就是工厂模式,函数能跟进不同的参数,实例化不同的子类对象,但是如果子类比较多,就会比较乱。就会对目前的简单工厂模式进行改进。
2,layer注册REGISTER_LAYER_CLASS(XXX)
在每个XXXLayer的cpp中,都有一个REGISTER_LAYER_CLASS的宏,正是这个宏,才使得caffe能找到并实例化相应的layer类。定义是在layer_factory.hpp中,是这样的:
#define REGISTER_LAYER_CLASS(type) \
template <typename Dtype> \
shared_ptr<Layer<Dtype> > Creator_##type##Layer(const LayerParameter& param) \
{ \
return shared_ptr<Layer<Dtype> >(new type##Layer<Dtype>(param)); \
} \
REGISTER_LAYER_CREATOR(type, Creator_##type##Layer)
这里##是宏的一些语法,表示把宏参数和##前后的内容连起来。例如我们自己定义MyAwesomeLayer层,就会加上REGISTER_LAYER_CLASS(MyAwesome)。代码相当于:
template <typename Dtype>
shared_ptr<Layer<Dtype> > Creator_MyAwesomeLayer(const LayerParameter& param){
return shared_ptr<Layer<Dtype> >(new MyAwesomeLayer<Dtype>(param));
}
REGISTER_LAYER_CREATOR(MyAwesome, Creator_MyAwesomeLayer)
上面两个代码块,第一个是实例化相应的层,第二块是定义了一个宏:
#define REGISTER_LAYER_CREATOR(type, creator) \
static LayerRegisterer<float> g_creator_f_##type(#type, creator<float>); \
static LayerRegisterer<double> g_creator_d_##type(#type, creator<double>); \
这里#它代表把宏的参数变成字符串。具体到这里,就等于写了以下代码:
static LayerRegisterer<float> g_creator_f_MyAwesome("MyAwesome", Creator_MyAwesomeLayer<float>);
static LayerRegisterer<double> g_creator_d_MyAwesome("MyAwesome", Creator_MyAwesomeLayer<double>);
这里创建了两个LayerRegisterer类的静态对象,给构造函数传了两个参数。一个是字符串,是我们的Layer的名字,另一个是刚刚创建的Creator_MyAwesomeLayer方法的函数指针。创建这两个对象是在做什么呢?看它们的定义:
template <typename Dtype>
class LayerRegisterer {
public:
LayerRegisterer(const string& type,
shared_ptr<Layer<Dtype> > (*creator)(const LayerParameter&)) {
// LOG(INFO) << "Registering layer type: " << type;
LayerRegistry<Dtype>::AddCreator(type, creator);
}
};
其实创建它们的过程不过是调用了AddCreator这个函数,把传入的字符串和函数指针又传给了它。
// Adds a creator.
static void AddCreator(const string& type, Creator creator) {
CreatorRegistry& registry = Registry();
CHECK_EQ(registry.count(type), 0)
<< "Layer type " << type << " already registered.";
registry[type] = creator;
}
这里面是通过Registry方法拿到了一个registry指针,关键步骤是:registry["MyAwesome"] = creator。这看着像是一个类似Python里的字典的东西,也就是个哈希表,键是Layer的类型,值是一个能返回这个Layer对象的函数。继续找Registry的定义:
typedef shared_ptr<Layer<Dtype> > (*Creator)(const LayerParameter&);
typedef std::map<string, Creator> CreatorRegistry;
static CreatorRegistry& Registry() {
static CreatorRegistry* g_registry_ = new CreatorRegistry();
return *g_registry_;
}
这里我们看到static CreatorRegistry* g_registry_ = new CreatorRegistry();是进行实例化,但是前面添加了static。最初执行了一次,g_registry_放在静态内存里,函数结束了是不会消失的!后面每次调用并不会执行new,返回的都是同一个东西的指针!
另外,typedef shared_ptr<Layer <Dtype> > (*Creator)(const LayerParameter&)这句话也有点莫名其妙,这个typedef是啥意思。其实,这和typedef int MYINT是类似的。后者是给int类起了个别名叫MYINT,然后就能这样写:MYINT a=10; 前者其实是给一类函数指针起了个别名,这类函数指针都是以LayerParameter&做参数,返回shared_ptr<<Layer <Dtype> >。 假如这样写: Creator pcreator;, 那pcreator就是一个这样的函数指针了。