0
\$\begingroup\$

I am working on a project that involves quite a lot of instantiated objects, with tons of properties, most of which have only a few possible values, so the prospect of saving on memory is quite significant.

I wanted "portable" bitfields in order to be able to use raw binary serialization and deserialization, so memory is saved in serialized form as well, plus the same data works across different platforms and compilers, so standard bitfields are out of the question, since the standard does not enforce and thus does not guarantee uniformity across different implementations.

However, writing those by hand turned out to be such a tedious drag, so I decided to write a little tool to generate the code for me. So this question is not really about reviewing the generator code but the generated code, I really would appreciate an expert looking at it, since I am still fairly new (2.5 years of casually using) in C++, so it is entirely possible I've missed something that would cause the generated code to break.

The generator itself is intended to work with Qt (thus the quint... types) and can work with different "base width" types from 8 to 64 bit. If the fields do not fit in a single "base type" an array of the latter is used.

Here are two use cases:

A single 32bit container with fields of width 1, 2, 3, 4, 5, 6, 7 bits respectively

public:
quint32 newField0() const { return flags & 1; }
void setNewField0(quint32 v) { flags = (flags & 4294967294) | v; }

quint32 newField1() const { return (flags & 6) >> 1; }
void setNewField1(quint32 v) { flags = (flags & 4294967289) | (v << 1); }

quint32 newField2() const { return (flags & 56) >> 3; }
void setNewField2(quint32 v) { flags = (flags & 4294967239) | (v << 3); }

quint32 newField3() const { return (flags & 960) >> 6; }
void setNewField3(quint32 v) { flags = (flags & 4294966335) | (v << 6); }

quint32 newField4() const { return (flags & 31744) >> 10; }
void setNewField4(quint32 v) { flags = (flags & 4294935551) | (v << 10); }

quint32 newField5() const { return (flags & 2064384) >> 15; }
void setNewField5(quint32 v) { flags = (flags & 4292902911) | (v << 15); }

quint32 newField6() const { return (flags & 266338304) >> 21; }
void setNewField6(quint32 v) { flags = (flags & 4028628991) | (v << 21); }

private:
quint32 flags;

A byte array with the same fields

public:
quint8 newField0() const { return flags[0] & 1; }
void setNewField0(quint8 v) { flags[0] = (flags[0] & 254) | v; }

quint8 newField1() const { return (flags[0] & 6) >> 1; }
void setNewField1(quint8 v) { flags[0] = (flags[0] & 249) | (v << 1); }

quint8 newField2() const { return (flags[0] & 56) >> 3; }
void setNewField2(quint8 v) { flags[0] = (flags[0] & 199) | (v << 3); }

quint8 newField3() const { return flags[1] & 15; }
void setNewField3(quint8 v) { flags[1] = (flags[1] & 240) | v; }

quint8 newField4() const { return flags[2] & 31; }
void setNewField4(quint8 v) { flags[2] = (flags[2] & 224) | v; }

quint8 newField5() const { return flags[3] & 63; }
void setNewField5(quint8 v) { flags[3] = (flags[3] & 192) | v; }

quint8 newField6() const { return flags[4] & 127; }
void setNewField6(quint8 v) { flags[4] = (flags[4] & 128) | v; }

private:
quint8 flags[5];

A 64 bit container with 6 10bit fields:

public:
quint64 newField0() const { return flags & 1023; }
void setNewField0(quint64 v) { flags = (flags & 18446744073709552000) | v; }

quint64 newField1() const { return (flags & 1047552) >> 10; }
void setNewField1(quint64 v) { flags = (flags & 18446744073708503000) | (v << 10); }

quint64 newField2() const { return (flags & 1072693248) >> 20; }
void setNewField2(quint64 v) { flags = (flags & 18446744072636858000) | (v << 20); }

quint64 newField3() const { return (flags & 1098437885952) >> 30; }
void setNewField3(quint64 v) { flags = (flags & 18446742975271666000) | (v << 30); }

quint64 newField4() const { return (flags & 1124800395214848) >> 40; }
void setNewField4(quint64 v) { flags = (flags & 18445619273314337000) | (v << 40); }

quint64 newField5() const { return (flags & 1151795604700004400) >> 50; }
void setNewField5(quint64 v) { flags = (flags & 17294948469009547000) | (v << 50); }

private:
quint64 flags;

enter image description here

Here is the full source code (its not C++ but QML and JavaScript)

\$\endgroup\$
1
\$\begingroup\$

The generated masks for setting a value are wrong.

I would use the following code which generates the correct masks.

template<typename T>
T getField(T flags, unsigned startbit, unsigned len) {
    T mask = static_cast<T>(((1 << (startbit + len)) - 1) >> startbit);
    return (flags >> startbit) & mask;
};
template<typename T>
T setField(T v, T flags, unsigned startbit, unsigned len) {
    T mask = static_cast<T>((((1 << (startbit + len)) - 1) >> startbit) << startbit);
    return (flags & ~mask) | v;
};

and just generate this code:

quint32 newField0() const { return getField(flags, 0, 1); }
void setNewField0(quint32 v) { flags = setField(v, flags, 0, 1); }

quint32 newField1() const { return getField(flags, 1, 2); }
void setNewField1(quint32 v) { flags = setField(v, flags, 1, 2); }

quint32 newField2() const { return getField(flags, 3, 3); }
void setNewField2(quint32 v) { flags = setField(v, flags, 3, 3); }

...

I'm sure there's a better way but you can use the preprocessor to generate the accessors, so no need for your generator at all.

\$\endgroup\$
  • \$\begingroup\$ The masks for most widths seem ok to me, but there seems to be a problem with the 64 bit ones, seems like JS cannot quite parse or express them right. \$\endgroup\$ – dtech Jan 10 '15 at 11:54
  • \$\begingroup\$ "The largest positive whole number that can therefore be accurately represented is 2^53"... that's probably why \$\endgroup\$ – dtech Jan 10 '15 at 12:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.