定义c++位结构

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);
}
【完】









  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值