笔者在阅读Klayout源码时,发现了一段构建一个遍历继承自同一父类的派生子类的迭代器的优秀代码,特写此博客,记录自己所得
Klayout是版图设计工具,版图文件格式有很多种,例如gds、oasis等,自然需要构建多种版图文件格式的reader,在Klayout读取版图文件之前,尚无法判断待读取的版图文件属于哪种文件格式,在读取了版图文件的前几个字节后,即可知道其对应的文件格式,创建对应的文件格式的reader
问题:如何写这一段的业务代码呢?
很容易想到的一种做法是:在读取了版图文件的前几个字节后,对应版图文件个数,创建对应的if else 条件分支判断,然后创建对应的文件格式的reader
这种做法的缺点:如果Klayout需要支持新的版图文件格式,则需要对上述代码进行修改,而提倡的做法是“对修改关闭,对扩展开放”
Klayout的解法,如下图所示
以下给出详细的代码
classRegistry.h
#ifndef CLASSREGISTRY_H
#define CLASSREGISTRY_H
#include <typeinfo>
#include <string>
template <class X>
class Registrar;
/**
* @brief A helper object that is used to manage the registered classes
*/
template <class X>
class RegistrarNode
{
private:
RegistrarNode ()
: mp_object (0), m_position (0), mp_next (0)
{
// .. nothing else ..
}
~RegistrarNode ()
{
delete mp_object;
mp_object = 0;
}
X *mp_object;
int m_position;
std::string m_name;
RegistrarNode *mp_next;
friend class Registrar<X>;
friend class Registrar<X>::iterator;
};
/**
* @brief A generic class registration facility
*
* It can register a set of objects by instantiating a certain object
* and somewhere (else) instantiating an registrar.
* The objects are classified by a base class they are derived of.
* The collection of registered objects can be iterated.
* See tlClassRegistry.ut for an example.
*/
/**
* @brief A class representant that is registered
*/
template <class X>
class RegisteredClass
{
public:
/**
* @brief register an object of type X
*
* This will register the given object. The X pointer
* will become owned by the registrar if "owned" is true.
* The position parameter tells where to insert the class in the
* chain - higher positions come later.
* The name is an arbitrary string that is used for debugging purposes only.
*/
RegisteredClass (X *inst, int position = 0, const char *name = "")
{
Registrar<X> *instance = Registrar<X>::get_instance ();
if (! instance) {
instance = new Registrar<X> ();
Registrar<X>::set_instance (instance);
}
mp_node = instance->insert (inst, position, name);
}
~RegisteredClass ()
{
Registrar<X> *instance = Registrar<X>::get_instance ();
if (instance) {
// remove the associated object
instance->remove (mp_node);
if (instance->begin () == instance->end ()) {
// no more registered objects left - remove registrar
delete instance;
Registrar<X>::set_instance (0);
}
}
}
private:
RegistrarNode<X> *mp_node;
};
/**
* @brief A base class for the registrar types
*/
class RegistrarBase { };
/**
* @brief Sets the registrar instance by type
*/
void set_registrar_instance_by_type (const std::type_info &ti, RegistrarBase *rb);
/**
* @brief Gets the registrar instance by type
* Returns 0 if no registrar instance is set by this type;
*/
RegistrarBase* registrar_instance_by_type (const std::type_info &ti);
/**
* @brief The registrar capable of registering objects of type Y derived from X
*
* Objects of type Y derived from X can simply be created and registered by
* instantiation (statically) an object of tl::Registrar<X>::Class which
* will receive an new'ed object of type Y and register it in the <X> registration
* space. This object will then be owned by the registrar and is destroyed upon
* shut down.
*/
template <class X>
class Registrar
: public RegistrarBase
{
public:
class iterator
{
public:
iterator (RegistrarNode<X> *p)
: mp_pos (p)
{
// .. nothing yet ..
}
bool operator== (iterator d) const
{
return mp_pos == d.mp_pos;
}
bool operator!= (iterator d) const
{
return mp_pos != d.mp_pos;
}
iterator &operator++ ()
{
mp_pos = mp_pos->mp_next;
return *this;
}
const std::string ¤t_name () const
{
return mp_pos->m_name;
}
int current_position () const
{
return mp_pos->m_position;
}
X &operator* () const
{
return *(mp_pos->mp_object);
}
X *operator-> () const
{
return mp_pos->mp_object;
}
private:
RegistrarNode<X> *mp_pos;
};
/**
* @brief Constructor
*/
Registrar <X> ()
: mp_first (0)
{
// .. nothing yet ..
}
/**
* @brief begin() iterator over the registered objects
*/
static iterator begin ()
{
if (get_instance ()) {
return iterator (get_instance ()->mp_first);
} else {
return iterator (0);
}
}
/**
* @brief end() iterator over the registered objects
*/
static iterator end ()
{
return iterator (0);
}
static Registrar<X> *get_instance ()
{
return static_cast<Registrar<X> *> (registrar_instance_by_type (typeid (X)));
}
static void set_instance (Registrar<X> *instance)
{
set_registrar_instance_by_type (typeid (X), instance);
}
private:
friend class iterator;
template <class Y> friend class RegisteredClass;
RegistrarNode<X> *insert (X *cls, int position, const std::string &name)
{
RegistrarNode<X> **link = &mp_first;
while (*link && (*link)->m_position < position) {
link = &((*link)->mp_next);
}
RegistrarNode<X> *node = new RegistrarNode<X> ();
node->mp_object = cls;
node->m_position = position;
node->m_name = name;
node->mp_next = *link;
*link = node;
return node;
}
void remove (RegistrarNode<X> *node)
{
RegistrarNode<X> **link = &mp_first;
while (*link && *link != node) {
link = &((*link)->mp_next);
}
if (*link) {
RegistrarNode<X> *node = *link;
*link = node->mp_next;
delete node;
}
}
RegistrarNode<X> *mp_first;
};
#endif
classRegistry.cpp
#include "classRegistry.h"
#include <map>
typedef std::map<const std::type_info *, RegistrarBase *> inst_map_type;
static inst_map_type s_inst_map;
void set_registrar_instance_by_type (const std::type_info &ti, RegistrarBase *rb)
{
if (rb) {
s_inst_map[&ti] = rb;
} else {
s_inst_map.erase (&ti);
}
}
RegistrarBase* registrar_instance_by_type (const std::type_info &ti)
{
inst_map_type::const_iterator im = s_inst_map.find (&ti);
if (im != s_inst_map.end ()) {
return im->second;
} else {
return 0;
}
}
InputStream.h
#ifndef INPUTSTREAM_H
#define INPUTSTREAM_H
class InputStream {
private:
int type;
int data;
public:
InputStream(int type, int data);
int getType();
int getData();
};
#endif
InputStream.cpp
#include "InputStream.h"
InputStream::InputStream(int type, int data) {
this->type = type;
this->data = data;
}
int InputStream::getType() {
return type;
}
int InputStream::getData() {
return data;
}
LayerMap.h
#ifndef LAYERMAP_H
#define LAYERMAP_H
class LayerMap {
private:
int lm;
public:
LayerMap(int data);
int getData();
};
#endif
LayerMap.cpp
#include "LayerMap.h"
LayerMap::LayerMap(int data) {
lm = data;
}
int LayerMap::getData() {
return lm;
}
ReaderBase.h
#ifndef READERBASE_H
#define READERBASE_H
#include "InputStream.h"
#include "LayerMap.h"
class ReaderBase {
public:
virtual LayerMap* read(InputStream& s) = 0;
};
#endif
GDS2Reader.h
#ifndef GDS2READER_H
#define GDS2READER_H
#include "ReaderBase.h"
#include "GDS2Reader.h"
#include "LayerMap.h"
class GDS2Reader : public ReaderBase
{
public:
GDS2Reader() {}
LayerMap* read(InputStream& s);
};
#endif
GDS2Reader.cpp
#include "GDS2Reader.h"
#include <iostream>
LayerMap* GDS2Reader::read(InputStream& s) {
std::cout << "gds2_reader" << std::endl;
return new LayerMap(s.getData());
}
OASISReader.h
#ifndef OASISREADER_H
#define OASISREADER_H
#include "ReaderBase.h"
#include "LayerMap.h"
class OASISReader : public ReaderBase
{
public:
OASISReader() {}
LayerMap* read(InputStream& s);
};
#endif
OASISReader.cpp
#include "OASISReader.h"
#include <iostream>
LayerMap* OASISReader::read(InputStream& s) {
std::cout << "oasis_reader" << std::endl;
return new LayerMap(s.getData());
}
StreamFormat.h
#ifndef STREAMFORMAT_H
#define STREAMFORMAT_H
#include "InputStream.h"
#include "ReaderBase.h"
#include "classRegistry.h"
class StreamFormat {
public:
virtual bool detect(InputStream& s) = 0;
virtual ReaderBase* create_reader() = 0;
};
#endif
GDS2Format.cpp
#include "GDS2Reader.h"
#include "StreamFormat.h"
class GDS2Format : public StreamFormat {
bool detect(InputStream& s) {
if(s.getType() == 1) {
return true;
}
else {
return false;
}
}
ReaderBase* create_reader() {
return new GDS2Reader();
}
};
static RegisteredClass<StreamFormat> gds2_decl(new GDS2Format());
OASISFormat.cpp
#include "OASISReader.h"
#include "StreamFormat.h"
class OASISFormat : public StreamFormat {
bool detect(InputStream& s) {
if(s.getType() == 2) {
return true;
}
else {
return false;
}
}
ReaderBase* create_reader() {
return new OASISReader();
}
};
static RegisteredClass<StreamFormat> oasis_decl(new OASISFormat());
Reader.h
#ifndef READER_H
#define READER_H
#include "ReaderBase.h"
#include "InputStream.h"
#include "LayerMap.h"
#include "classRegistry.h"
#include "StreamFormat.h"
class Reader {
private:
ReaderBase* mp_actual_reader;
InputStream& m_stream;
public:
Reader(InputStream& stream);
LayerMap* read(InputStream& stream);
};
#endif
Reader.cpp
#include "Reader.h"
Reader::Reader(InputStream& stream) : mp_actual_reader(0), m_stream(stream) {
for(auto iter = Registrar<StreamFormat>::begin(); iter != Registrar<StreamFormat>::end(); ++iter) {
if(iter->detect(m_stream)) {
mp_actual_reader = iter->create_reader();
}
}
}
LayerMap* Reader::read(InputStream& stream) {
return mp_actual_reader->read(m_stream);
}
main.cpp
#include "InputStream.h"
#include "Reader.h"
#include "classRegistry.h"
#include "LayerMap.h"
#include <iostream>
int main() {
InputStream stream(1, 3); // simulate reading GDS2 files
Reader reader(stream);
LayerMap* lm = reader.read(stream);
std::cout << lm->getData() << std::endl; // simulate printing the content of the GDS2 files
return 0;
}
编译命令:
g++ classRegistry.cpp InputStream.cpp LayerMap.cpp ReaderBase.h GDS2Reader.cpp OASISReader.cpp StreamFormat.h GDS2Format.cpp OASISFormat.cpp Reader.cpp main.cpp -o main
./main
执行结果如下所示:
gds2_reader
3
讨论:
1.如果Klayout需支持读取新格式的版图文件,参考GDS2Reader、GDS2Format添加即可
2.上面的例子只列举一种多个子类继承父类的情况,其实上面的代码可复用性更强,适用于多种多个子类继承父类的情况(例如Klayout支持修图,可读取PNG、JPEG等图片,则可创建PictureReaderBase、PNGReader、PNGFormat、JPEGReader、JPEGFormat,需说明的是针对修图功能创建的迭代器与读取版图文件创建的迭代器不是同一个迭代器)
3.笔者在开始看classRegistry.h中的注册器基类RegistrarBase时,认为其没有什么用,在尝试删除这个类时,却发现注册器子类Registrar是一个模板类,如果将Registrar直接作为函数参数的类型,会增加代码的复杂度,让注册器子类Registrar继承注册器基类RegistrarBase,传参时使用RegistrarBase类型的参数,会使代码更加简洁