Casting base * to derived *

S

Spoon

Hello everyone,

I'm writing code where I can receive two kinds of packets.
The first 12 octets are common to both packet types, i.e. they have
the same semantics.

The 1st type has 4 more octets in the header, then the payload.
The 2nd type has 16 more octets in the header, then the payload.

I was thinking I'd write a base class with two derived classes:

class BasePacket {
public:
BasePacket(uint8_t *buf, int size)
{ allocate memory and copy buf to buffer }
virtual uint8_t *Payload() = 0;
int type() { return buffer[1]; }
private:
uint8_t *buffer;
};

class Type1_Packet : public BasePacket {
public:
/* No constructor for this class -- see below */
virtual uint8_t *Payload() { return buffer + 12 + 4; }
+ access functions to the additional header parts
}

class Type2_Packet : public BasePacket {
public:
/* No constructor for this class -- see below */
virtual uint8_t *Payload() { return buffer + 12 + 16; }
+ access functions to the additional header parts
}

And my intention was to write a function that would listen on
several sockets and return a true BasePacket *

BasePacket *the_listener()
{
...
return new BasePacket(somebuf, somesize);
}

Is it safe to then write:

BasePacket *packet = the_listener();
if (packet->type() == XX)
{
/* we know the packet is in fact a Type1_Packet */
Type1_Packet *foo = <some_kind_of_cast> packet;
do something with foo;
foo->Payload() would return buffer+16
}
else if (packet->type() == YY)
{
/* we know the packet is in fact a Type2_Packet */
Type2_Packet *foo = <some_kind_of_cast> packet;
do something with foo;
foo->Payload() would return buffer+28
}

In other words, is it safe to cast a true BasePacket * into a pointer to
one of the derived objects? (Given that the derived classes have no
additional members.)



Another way I've considered is:

class BaseClass
{
public:
BaseClass(int a) : toto(a) { }
private:
int toto;
};

class Derived1 : public BaseClass
{
public:
Derived1(double x) : BaseClass(123), tutu(x) { }
private:
double tutu;
};

class Derived2 : public BaseClass
{
public:
Derived2(long x) : BaseClass(456), zozo(x) { }
private:
long zozo;
};

#include <list>
void add_to_list(int i, std::list<BaseClass *> queue)
{
if (i)
{
Derived1 *toto = new Derived1(3.1416);
queue.push_back(toto);
}
else
{
Derived2 *toto = new Derived2(987654);
queue.push_back(toto);
}
}
int main()
{
std::list<Derived1 *> q1;
add_to_list(1, q1);
return 0;
}

But that doesn't work either.



Otherwise, I imagine there is a better way to do what I want to do.

Regards.
 
G

Gernot Frisch

In other words, is it safe to cast a true BasePacket * into a
pointer to
one of the derived objects? (Given that the derived classes have no
additional members.)

You should turn RTTI on, and perform a dynamic_cast. This way you can
test if the base* is pointing to a derived* object.
 
P

Papastefanos Serafeim

Spoon said:
Hello everyone,

I'm writing code where I can receive two kinds of packets.
The first 12 octets are common to both packet types, i.e. they have
the same semantics.

The 1st type has 4 more octets in the header, then the payload.
The 2nd type has 16 more octets in the header, then the payload.

I was thinking I'd write a base class with two derived classes:

class BasePacket {
public:
BasePacket(uint8_t *buf, int size)
{ allocate memory and copy buf to buffer }
virtual uint8_t *Payload() = 0;
int type() { return buffer[1]; }
private:
uint8_t *buffer;
};

class Type1_Packet : public BasePacket {
public:
/* No constructor for this class -- see below */
virtual uint8_t *Payload() { return buffer + 12 + 4; }
+ access functions to the additional header parts
}

class Type2_Packet : public BasePacket {
public:
/* No constructor for this class -- see below */
virtual uint8_t *Payload() { return buffer + 12 + 16; }
+ access functions to the additional header parts
}

And my intention was to write a function that would listen on
several sockets and return a true BasePacket *

BasePacket *the_listener()
{
...
return new BasePacket(somebuf, somesize);
}

Is it safe to then write:

BasePacket *packet = the_listener();
if (packet->type() == XX)
{
/* we know the packet is in fact a Type1_Packet */
Type1_Packet *foo = <some_kind_of_cast> packet;
do something with foo;
foo->Payload() would return buffer+16
}
else if (packet->type() == YY)
{
/* we know the packet is in fact a Type2_Packet */
Type2_Packet *foo = <some_kind_of_cast> packet;
do something with foo;
foo->Payload() would return buffer+28
}

In other words, is it safe to cast a true BasePacket * into a pointer to
one of the derived objects? (Given that the derived classes have no
additional members.)



Another way I've considered is:

class BaseClass
{
public:
BaseClass(int a) : toto(a) { }
private:
int toto;
};

class Derived1 : public BaseClass
{
public:
Derived1(double x) : BaseClass(123), tutu(x) { }
private:
double tutu;
};

class Derived2 : public BaseClass
{
public:
Derived2(long x) : BaseClass(456), zozo(x) { }
private:
long zozo;
};

#include <list>
void add_to_list(int i, std::list<BaseClass *> queue)
{
if (i)
{
Derived1 *toto = new Derived1(3.1416);
queue.push_back(toto);
}
else
{
Derived2 *toto = new Derived2(987654);
queue.push_back(toto);
}
}
int main()
{
std::list<Derived1 *> q1;
add_to_list(1, q1);
return 0;
}

But that doesn't work either.



Otherwise, I imagine there is a better way to do what I want to do.

Regards.

About the first method you write:

The the_listener method must not return a new BasePacket();
but it has to return a new Type2_Packet or Type1_Packet,
depending on the actual type of that packet.

Also, the derived classes have to have constructors. Everything
else seems fine - but it'd be better to use RTTI to know the type
of each packet instead of using a specific field for that.


Serafeim
 
P

Pete Becker

Spoon wrote:

BasePacket *the_listener()
{
...
return new BasePacket(somebuf, somesize);
}

Is it safe to then write:

BasePacket *packet = the_listener();
if (packet->type() == XX)
{
/* we know the packet is in fact a Type1_Packet */
Type1_Packet *foo = <some_kind_of_cast> packet;

No, absolutely not. Look at the_listener again: it returns an object
whose dynamic type is BasePacket, not Type1_Packet and not Type2_Packet.

Since the first byte that you see determines the actual type that you
need, don't bother with that intermediate BasePacket object. Just create
the thing you need. If that's not feasible, create a new object of the
appropriate type when you can.
 
E

Earl Purple

Spoon said:
I'm writing code where I can receive two kinds of packets.
The first 12 octets are common to both packet types, i.e. they have
the same semantics.

The 1st type has 4 more octets in the header, then the payload.
The 2nd type has 16 more octets in the header, then the payload.
I was thinking I'd write a base class with two derived classes:

class BasePacket {
public:
BasePacket(uint8_t *buf, int size)
{ allocate memory and copy buf to buffer }
virtual uint8_t *Payload() = 0;
int type() { return buffer[1]; }
private:
uint8_t *buffer;

[ note: needs virtual destructor ]
};

class Type1_Packet : public BasePacket {
public:
/* No constructor for this class -- see below */
virtual uint8_t *Payload() { return buffer + 12 + 4; }
+ access functions to the additional header parts
}

class Type2_Packet : public BasePacket {
public:
/* No constructor for this class -- see below */
virtual uint8_t *Payload() { return buffer + 12 + 16; }
+ access functions to the additional header parts
}

And my intention was to write a function that would listen on
several sockets and return a true BasePacket *

BasePacket *the_listener()
{
...
return new BasePacket(somebuf, somesize);
}

Is it safe to then write:
/* then downcasts */

It's not a matter of safety but extensibility and abstraction, which
you do not have here. At the top level you know what type you have, or
can derive it. To add new types you have to go around modifying switch
statements.

Now each package type has a unique signature and we will assume that
will continue to be the same. But it should stop at that.

You should have a table of handlers, and for each signature, you should
look up the appropriate handler and call it, using the raw set of bytes
as the parameter. It is at this point you bring in the polymorphism.
There should be no casting back.
Otherwise, I imagine there is a better way to do what I want to do.

Yes, I have just told you. And it should not involve any casting at
all. You will just need all your handlers to have access to this table
to add themselves to it.

This is assuming, by the way, that you do not want to put all the
functionality into the messages themselves, i.e. give them all the
virtual methods you require to function.
 
S

Spoon

Earl said:
Spoon said:
I'm writing code where I can receive two kinds of packets.
The first 12 octets are common to both packet types, i.e. they have
the same semantics.

The 1st type has 4 more octets in the header, then the payload.
The 2nd type has 16 more octets in the header, then the payload.
I was thinking I'd write a base class with two derived classes:

class BasePacket {
public:
BasePacket(uint8_t *buf, int size)
{ allocate memory and copy buf to buffer }
virtual uint8_t *Payload() = 0;
int type() { return buffer[1]; }
private:
uint8_t *buffer;


[ note: needs virtual destructor ]
};


class Type1_Packet : public BasePacket {
public:
/* No constructor for this class -- see below */
virtual uint8_t *Payload() { return buffer + 12 + 4; }
+ access functions to the additional header parts
}

class Type2_Packet : public BasePacket {
public:
/* No constructor for this class -- see below */
virtual uint8_t *Payload() { return buffer + 12 + 16; }
+ access functions to the additional header parts
}

And my intention was to write a function that would listen on
several sockets and return a true BasePacket *

BasePacket *the_listener()
{
...
return new BasePacket(somebuf, somesize);
}

Is it safe to then write:

/* then downcasts */

It's not a matter of safety but extensibility and abstraction, which
you do not have here. At the top level you know what type you have, or
can derive it. To add new types you have to go around modifying switch
statements.

Now each package type has a unique signature and we will assume that
will continue to be the same. But it should stop at that.

You should have a table of handlers, and for each signature, you should
look up the appropriate handler and call it, using the raw set of bytes
as the parameter. It is at this point you bring in the polymorphism.
There should be no casting back.

Otherwise, I imagine there is a better way to do what I want to do.


Yes, I have just told you. And it should not involve any casting at
all. You will just need all your handlers to have access to this table
to add themselves to it.

This is assuming, by the way, that you do not want to put all the
functionality into the messages themselves, i.e. give them all the
virtual methods you require to function.

If I was to implement it all in main() I'd write something along
the lines of:

list<Type1_Packet *> queue1;
list<Type2_Packet *> queue2;
uint8_t *raw_packet = Receive_Raw_Packet(&size)
switch (raw_packet[1])
{
case XX:
Type1_Packet *p = new(raw_packet, size);
queue1.push_back(p);
break;
case YY:
Type2_Packet *p = new(raw_packet, size);
queue2.push_back(p);
break;
default:
//raise unknown type exception or handle error
break;
}

But if I were to hide the switch/case in a separate function, I don't
see how I can pass the queues (there may be a variable number of
Type1_Packet queues and Type2_Packet queues).

Regards.
 
E

Earl Purple

Spoon said:
If I was to implement it all in main() I'd write something along
the lines of:

list<Type1_Packet *> queue1;
list<Type2_Packet *> queue2;
uint8_t *raw_packet = Receive_Raw_Packet(&size)
switch (raw_packet[1])
{
case XX:
Type1_Packet *p = new(raw_packet, size);
queue1.push_back(p);
break;
case YY:
Type2_Packet *p = new(raw_packet, size);
queue2.push_back(p);
break;
default:
//raise unknown type exception or handle error
break;
}

We'll ignore the lists for now. Let's just create an abstract base
class:

class PacketHandler
{
public:
virtual ~PacketHandler();
virtual handlePacket( const vector< uint8_t > & packet ) = 0;
};

PacketHandler* handler_table[ 256 ]; // or use mnemonic rather than
magic number

vector< uint8_t > packet;
while ( receivePacket( packet ) )
{
// assuming packet is not empty
uint8_t key = packet[0];
PacketHandler * handler = handler_table[ key ];
if ( handler )
{
handler->handlePacket( packet );
}
else
{
// handle error
}
}

Now you can extend it up to 256 different handlers. If that's not
enough, then make the header bigger.

Your main loop knows only of the existence of message handlers and has
no dependency on any particular handler.

And look, no casting.

Note you will probably refine the above to handle thread-safety issues,
memory management etc. The above is just a guideline for the model.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top