0) using reinterpret_cast on a char* is a Really Bad Idea. Change your base constructor to take a Header pointer. Or don't since they don't share anything, there's no real reason to to use inheritance here.
Maybe you're leaving out a bunch of stuff in the base Header for clarity's sake, I dunno. But as written, you don't need the base header pointer in the first place.
1) Make HeaderA inherit from Header.
2) DerivedA's constructor takes a HeaderA* parameter, which is then passed to the base class's Header*-based constructor.
3) DerivedA keeps a separate copy of what it was passed in, with the correct type.
So something like
class Base {
public:
virtual ~Base() {} // virtual base destructor! Very important.
Base(Header* foo)
: header(foo) {
}
private:
Header* header;
...
};
// at the very least, you probably want a virtual destructor here too
struct Header {
virtual ~Header() {}
};
struct HeaderA : public Header {
...
};
class DerivedA : public Base {
public:
DerivedA(HeaderA* header) :
: Base(header)
, headerA(header)
{
...
}
private:
HeaderA* headerA;
};
Note that the ownership of HeaderA isn't addressed here At All. You might want to use a std::unique_ptr<Header>
in the base class, and let RAII handle it for you everywhere else... rather than using references, like I have in the above code. Then at least there'd be some point to having it in the base class even if the header was completely empty.
Back to that cast from char*
to Header*
. I presume you're writing the header out with memcpy()
or something like it either to a file, or a packet or some such, in an effort to be efficient.
Don't.
In both AAA MMORPG code bases I've worked on, each field of each struct was written out explicitly (to packets or files). It may have worked like an overly verbose memcpy()
in some cases, but we made the effort, because the safety was Worth The Extra Effort. If you continue to do things the way you are, everything in your header has to be Right There.
- no pointers
- strings have to be fixed size arrays, arrays that will be mostly empty most of the time, because they're sized for your worst case scenario.
- object pointers have to be replaced with object IDs, and that the object IDs have to be consistent between when they were written, and when they were read. And those IDs have to be stored in the struct, which means looking them up every time you have to use them.
- You're locked into either "big endian" or "little endian" at write time, and heaven help you if your write "endian-ness" is different from your read "endian-ness". ("What the hell are big endian and little endian?" Google it. Right now. Seriously.)
By writing out fields individually you can do things like
- write everything out in a particular endian-ness.
- convert object pointers to object IDs.
- write out a length and then the correct number of characters/bytes rather than a fixed/mostly-empty number of bytes.
- encrypt field values (MMOs can't trust the data packets not to be sniffed so players can cheat)
- change field order (it's fun to scramble your packets' field order and watch the hackers crash when they connect the first time, at which point you know which accounts to flag for Further Investigation. Yes, this is one of several reasons why your old version of some client won't work with the new version of the corresponding server.)