简体   繁体   中英

Extending the functionality of std::vector<uint8_t> - namespace, composition or inheritance?

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

Composition

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?

Inheritance

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM