C里面有按位定义结构的方式,例如:
struct SBit {
int a:1;
int b:2;
};
结构中每个成员占有的bit数可以是任意的,而且结构中间不会有字节补齐导致的空bit
使用上述C结构可以满足大多数对内存按bit读写的应用,但如果字段间有间隔,
就需要人为插入空字段,显得不是那么方便了。
使用C++,我们想这样定义一个结构:
struct SBit {
BitValue<0,1> a;
BitValue<3,20> b;
};
其中,成员a占用第0bit开始的1bit长度空间,成员b占用第3bit开始的20bit长度空间。
另外为了实现对其读写操作,我们实现了一个基类,再为其提供内存的读写成员函数:
template<class T> struct BitStruct{
void write(unsigned char *buff, int len);
void read(const unsigned char *buff, int len)
};
由于基类读写函数需要知道操作的派生类类型,所以就通过模板参数将派生类型传下来。
修改SBit定义如下:
struct SBit : BitStruct<SBit> {
BitValue<0,1> a;
BitValue<3,20> b;
};
接下来,定义BitValue结构,这个结构作为内嵌类,定义在基类模板里,只允许被派生类引用:
template<int idx, int len>
struct BitValue {
enum {IDX=idx, LEN=len};
BitStruct *parent;
union {
int ivalue;
float fvalue;
};
BitValue() : parent(NULL), ivalue(0) {}
void operator = (int v) { ivalue = v; }
void operator int() const { return ivalue; }
void operator = (float v) { fvalue = v; }
void operator float() const { return fvalue; }
};
其中,parent指针指向了包含它的父结构,ivalue/fvalue保持它的值,另外还提供了操作符重载方法,
方便从int/float兼容的数据类型赋值,以及将其隐式转换为int/float类型。
准备工作到现在做的差不多了,接下来就是最关键的基类的read、write方法的实现。
先来思考一个问题:
由于read或者write需要读写派生类的数据成员,如何让基类知道派生类都定义了哪些成员?
C++没有动态语言的反射机制,这个功能似乎没有很直接的实现方法,C++唯一对每个数据成员都会执行的是ctor和dtor,
即构造和析构。我们构造一个对象,C++会自动执行对象的所有数据成员的构造函数,析构也是如此。
我们基类里通过模块参数知道了派生类类型,是否可以构造一个派生类的对象,来遍历其成员呢,答案是肯定的。
秘密就藏在了BitValue对象里,构造函数有普通构造和拷贝构造,为了得到派生类的每个BitValue,我们运用拷贝构造:
BitValue(BitValue &other) {
if (other.parent->w_)
other.parent->w_->set(other.IDX, other.LEN, other.ivalue);
else if (other.parent->r_)
other.ivalue = other.parent->r_->read(other.IDX, other.LEN);
else
ivalue = other.ivalue;
}
其中,r_和w_是BitStruct提供的一个bit位操作辅助对象,用于读取和写入特定的bit位
为了调用到上面的拷贝构造函数,我们将使用*this来构造一个派生类临时对象,最终将遍历到派生类的所有BitValue。
void write(unsigned char *buff, int len) {
BitWriterInOrder w(buff, len);
w_ = &w;
T value((T&)*this);
w_ = NULL;
}
void read(unsigned char *buff, int len) {
BitReaderInOrder r(buff, len);
r_ = &r;
T value((T&)*this);
r_ = NULL;
}
到这一步,细心的同学会发现,上述代码还剩下最后一个小问题,BitValue的parent指针在什么时候赋值?
这个问题要求我们从C++对象模型来推:
首先,基类自身的地址和派生类是相同的,同时我们的基类并不包含虚函数,因此派生类的对象模型为
a.派生类对象 = 基类数据 + 派生类定义的BitValue * 数量
b.基类的this指针就是派生类的this指针。
由于每个BitValue其实大小是相同的,我们可以由基类指针推导出派生类每个BitValue的地址,
并对其parent成员赋值,代码如下:
for (int i=0; i<(sizeof(T)-sizeof(BitStruct))/sizeof(BitValue<0,0>); ++i) {
*(BitStruct **)(void*)((unsigned char*)this + sizeof(BitStruct) + sizeof(BitValue<0,0>)*i) = this;
}
上述代码利用了parent是BitValue的第一个数据成员,它的地址就是BitValue的地址进行了简化书写。
现在我们就实现了最初相要的功能,使用如下代码进行测试:
struct MyData : BitStruct<MyData> {
BitValue<0,1> a;
BitValue<2,1> b;
BitValue<4,2> c;
};
void test() {
MyData src, dst;
src.a = 1;
src.b = 0;
src.c = 3.0f;
unsigned char buf[10];
src.write(buf, 10);
dst.read(buf, 10);
assert(src.a.ivalue == dst.a.ivalue);
assert(src.b.ivalue == dst.b.ivalue);
assert(src.c.fvalue == dst.c.fvalue);
}
【完】