Very often I need to provide a uint8_t
array to some third-party library. Usually the third-party library asks for a uint8_t*
, together with a length argument. Generally I use a std::vector<uint8_t>
and use its data()
and size()
methods to get this information which works a treat. Now I've often found myself wanting to create this vector<uint8_t>
using the <<
operator, similar to how std::stringstream
works, for example:
uint8_t first = 8;
uint8_t second = 3;
std::vector<uint8_t> raw;
raw << first
<< second;
Often I need to mix integers of different sizes - a few one-byte header bytes, then one four-byte value, then a one-byte crc. This <<
overload automatically takes care of this, for example:
uint32_t value = 0;
std::vector<uint8_t> raw;
raw << value;
int sz = raw.size(); // sz = 4
The operator<<
function would look somewhat like the following. Keep in mind that in order to split up into individual bytes I'd either define multiple operator<<
overloads, one for each type, or make a std::is_arithmetic
restricted template. I'm not showing this for simplicity.
std::vector<uint8_t>& operator << (std::vector<uint8_t>& msg, uint8_t const& value)
{
msg.push_back(value);
return msg;
}
Now I obviously want to restrict this functionality. Not every std::vector<uint8_t>
should have this functionality. One solution would be to define the operator<<
in namespace serial
and whenever the functionality is needed write using namespace serial;
in the required scope. While not a bad solution, I still think this is a little confusing. In the same scope I may have a different std::vector<uint8_t>
for which this functionality is not needed.
I'd ideally create a new type, Message
which allows for this functionality so the code becomes:
Message msg1;
msg1 << 4; // OK, I've defined this
uint8_t* ptr1 = msg1.data(); // get pointer to first element - needs to be defined in the Message class.
std::vector<uint8_t> msg2;
msg2 << 4; // not OK, not part of std::vector<uint8_t>
uint8_t* ptr2 = msg2.data(); // get pointer to first element, fine as it's in std::vector
Now I could use composition to make this struct, like this:
struct Message
{
std::vector<uint8_t> raw;
};
However that means that whenever you want to call a method of the vector ( size()
, data()
, begin()
, etc...) you need to call msg.raw.size()
, msg.raw.data()
, msg.raw.begin()
which isn't particularly elegant (in my opinion). Obviously you can add functions to the Message struct that replicate the original functionality, like:
size_t size() const { return raw.size() };
size_t size() const noexcept { return raw.size() };
However given the size of std::vector that's a lot of functions, not to mention you'd have to change them when std::vector changes. I don't necessary need all functions that std::vector has to offer, but where to draw the line?
As far as I know - you do not, ever, inherit from standard types. Then I saw this answer by Richard Hodges, who seems to have a pretty good reputation, give this as a solution to a different question:
// Edge is now a type, in the global namespace...
struct Edge : std::pair<VertexName, VertexName> {
using std::pair<VertexName, VertexName>::pair;
};
Does this mean I could do the following?
struct Message : std::vector<uint8_t> {
using std::vector<uint8_t>::vector;
};
Message msg1;
msg1 << 8; // works (provided I define the operator<< as shown above for Message)
int sz = msg1.size(); // works as Message is a std::vector, result: 4
std::vector<uint8_t> msg2;
msg2 << 8; // doesn't work, as intended
What about if I want to add a variable to it, so it becomes:
enum class Endian
{
lsb,
msb
};
struct Message : std::vector<uint8_t>
{
using std::vector<uint8_t>::vector;
Endian m_endian;
};
Concrete question: can I do the second approach as it suits my needs best, or will I be in trouble as I inherit from std::vector? Any advice on the best approach would be very much appreciated.
One solution I've used in the past for similar situations is to overload the operator->
to give acces to the underlying object you want to wrap.
struct Message
{
std::vector<uint8_t> raw;
std::vector<uint8_t>* operator->() {
return &raw;
}
};
When you return a pointer from operator->
that pointer will in turn also get dereferenced, so you can access any native vector functions using that.
Message m;
std::cout << m->data() << m->size();
And if you add your own methods you can access them as usual.
m.myOwnMethod();
You can also add your overloads of operator<<
for Message
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.