casting a struct to a class

  • Thread starter Leif Gruenwoldt
  • Start date
L

Leif Gruenwoldt

Is it possible to safely cast a struct to a class? The contents of both
are the same size, however there is the issue of the class having
virtual functions which make the size of the class slightly larger.
 
N

Noah Roberts

Leif said:
Is it possible to safely cast a struct to a class? The contents of both
are the same size, however there is the issue of the class having
virtual functions which make the size of the class slightly larger.

It is not safe to cast between unrelated types. If there is no
conversion from one to the other, either implicitly existing by
language design or added by you, then there is no safe conversion.
Therefore a static_cast will give a compiler error (you ARE using C++
casts and not C style right?) and you will know that the conversion is
unsafe when you change it to reinterpret_cast or write the conversion
routines.

If your struct and class are related types, for instance one inherits
from the other, then you can cast up the tree to the other type safely
and down the tree if you KNOW you are looking at a pointer or reference
to that subclass/struct.

There is no diff between a struct and class as far as main mechanics.
Default level of encapsulation is the only diff. A class can inherit
from a struct and visa-versa no problem.
 
L

Leif Gruenwoldt

Ok thanks Noah.

I should have given a bit more background about why I was wanting to do
this.

My code reads packets off the network that are of some defined format.
I have a struct that matches the format of the packet.

struct
{
int i;
char c
} packet_t;

In the past I would recv a packet and cast it to be of type packet_t.
i.e.

char raw_packet[SIZE] = recv(...);
packet_t * p = reinterpret_cast<packet_t *> ( raw_packet );

However now I would like to convert my struct to be a class so I have
something liket his:

class CPacket
{
public:

virtual ~packet() {};
virtual string toString() const;

private:
int i;
char c;
};

I was hoping I could continue on with my old ways and just cast the raw
packet I recieved into a class object like so:

char * raw_packet = recv(...);
CPacket p = reinterpret_cast<CPacket *>( raw_packet );

However when I do this type of cast the bits don't line up. The class
object is not the same size as the struct

sizeof( CPacket ) ! = sizeof( packet_t )

The class CPacket is 4 bytes I believe larger. I guess this is for the
vtable because of the virtual functions? Anyways, I guess i'm out of
luck. My only thought is to do something like this.

class CPacket
{
...

private:

packet_t m_packet; // instance of struct that matches packet
}
 
N

Noah Roberts

Leif said:
Ok thanks Noah.

I should have given a bit more background about why I was wanting to do
this.

My code reads packets off the network that are of some defined format.
I have a struct that matches the format of the packet.

struct
{
int i;
char c
} packet_t;

In the past I would recv a packet and cast it to be of type packet_t.
i.e.

char raw_packet[SIZE] = recv(...);
packet_t * p = reinterpret_cast<packet_t *> ( raw_packet );

What you are doing is icky anyway...you need another way so why not do
it the correct way?
However now I would like to convert my struct to be a class so I have
something liket his:

class CPacket
{
public:

virtual ~packet() {};
virtual string toString() const;

private:
int i;
char c;
};

CPacket
{
public:
CPacket(const char* raw)
{ memcpy(reinterpret_cast<char*>(&i), raw, sizeof(i)); memcpy(&c,
raw + sizeof(i), 1); }
...
private:
int i;
char c;
}

Note that the first memcpy is not exactly portable but is pretty
predictable. raw needs to have same byte alignment as reading machine
and all that...you need to know that the first X bytes are an int
padded and aligned just like your ints.
I was hoping I could continue on with my old ways and just cast the raw
packet I recieved into a class

You don't want to, really you don't.

Your other idea won't work either (virtual functions). Best you could
do, and I don't recommend it:

CPacket {
public:
CPacket(char * raw) { packet = reinterpret_cast<packet_t*>(raw); }

... no destructor deleting internal pointer!! Don't want it
either...

private:

struct packet_t { int i; char c; };
packet_t * packet;
}

after a read...

CPacket p(data); or CPacket * p = new CPacket(data)

At any rate, that isn't at all portable either. Better to do it right
 
L

leifer

At any rate, that isn't at all portable either. Better to do it right

I'm confused as to which you consider the "right way". Was it your
first example with the memcpy? How about what you mentioned earlier
with creating a class that inherits from the struct. How would that
work in terms of doing the packet read from network and creating an
instance of the new class?

If you have any other suggestions (like scrapping what we have here
altogether and trying something different) I'd be interested in hearing
that too.

Thanks in advance.
 
M

Markus Schoder

leifer said:
I'm confused as to which you consider the "right way". Was it your
first example with the memcpy? How about what you mentioned earlier
with creating a class that inherits from the struct. How would that
work in terms of doing the packet read from network and creating an
instance of the new class?

If you have any other suggestions (like scrapping what we have here
altogether and trying something different) I'd be interested in hearing
that too.

Maybe something like this works for you:

struct packet_t
{
int i;
char c;
};

struct CPacketBase
{
union
{
packet_t packet;
char raw_packet[sizeof(packet_t)];
}
};

class CPacket : public CPacketBase
{
public:
virtual ~CPacket() {}
virtual string toString() const;
};

And then you do:

CPacket cpacket;
recv(cpacket.raw_packet, sizeof(packet_t), ...);

Voila, no reinterpret_cast with all its aliasing issues any more.
 
M

Me

Leif said:
struct
{
int i;
char c
} packet_t;

In the past I would recv a packet and cast it to be of type packet_t.
i.e.

char raw_packet[SIZE] = recv(...);
packet_t * p = reinterpret_cast<packet_t *> ( raw_packet );

That's not guaranteed to work because of alignment issues (among other
things).
However now I would like to convert my struct to be a class so I have
something liket his:

class CPacket
{
public:

virtual ~packet() {};
virtual string toString() const;

private:
int i;
char c;
};

I was hoping I could continue on with my old ways and just cast the raw
packet I recieved into a class object like so:

char * raw_packet = recv(...);
CPacket p = reinterpret_cast<CPacket *>( raw_packet );

However when I do this type of cast the bits don't line up. The class
object is not the same size as the struct

sizeof( CPacket ) ! = sizeof( packet_t )

It's worse. The standard doesn't specify the layout of non-POD classes
at all. It can be something completely whackier than what you expect.
class CPacket
{
...

private:

packet_t m_packet; // instance of struct that matches packet
}

That's a pretty good solution. Are you just too lazy to type m_packet.c
instead of c? If you *really* want to do that, you can use:

class CPacket : private packet_t {
...
};

recv(sh, (packet_t*)this, sizeof(packet_t), flags);

But you have to be careful with this because the standards committee
used the C cast for this instead of inventing something like
private_cast so if you change the class later on to not derive from
packet_t, the code will compile without complaint but it will be
mysteriously broken. It's a very good idea to wrap up the cast inside a
member function so it only occurs in a single localized place.
 
B

benben

Leif said:
Ok thanks Noah.

I should have given a bit more background about why I was wanting to do
this.

My code reads packets off the network that are of some defined format.
I have a struct that matches the format of the packet.

struct
{
int i;
char c
} packet_t;

In the past I would recv a packet and cast it to be of type packet_t.
i.e.

char raw_packet[SIZE] = recv(...);
packet_t * p = reinterpret_cast<packet_t *> ( raw_packet );

However now I would like to convert my struct to be a class so I have
something liket his:

class CPacket
{
public:

virtual ~packet() {};
virtual string toString() const;

private:
int i;
char c;
};

I was hoping I could continue on with my old ways and just cast the raw
packet I recieved into a class object like so:

char * raw_packet = recv(...);
CPacket p = reinterpret_cast<CPacket *>( raw_packet );

However when I do this type of cast the bits don't line up. The class
object is not the same size as the struct

sizeof( CPacket ) ! = sizeof( packet_t )

The class CPacket is 4 bytes I believe larger. I guess this is for the
vtable because of the virtual functions? Anyways, I guess i'm out of
luck. My only thought is to do something like this.

class CPacket
{
...

private:

packet_t m_packet; // instance of struct that matches packet
}

First off, I don't think you actually need virtual member functions in
CPacket because you are unlikely to be inherited. So think thrice before
you make a member virtual.

Second, if you can successfully obtain a packet_t, then converting
packet_t to CPacket isn't much of a big deal. All you need is a constructor:

class CPacket{
int i; char c;
public:

CPacket(packet_t p):i(p.i), c(p.c){}
// other features...
};

And at last, wrap it all up with a function:

CPacket receive_packet(...)
{
char* primary = recv;
packet_t s = reinterpret_cast<packet_t*>(primary);
return CPacket(s);
}

Regards,
Ben
 
J

Jakob Bieling

Markus Schoder said:
leifer wrote:
Maybe something like this works for you:

struct packet_t
{
int i;
char c;
};

struct CPacketBase
{
union
{
packet_t packet;
char raw_packet[sizeof(packet_t)];
}
};

class CPacket : public CPacketBase
{
public:
virtual ~CPacket() {}
virtual string toString() const;
};

And then you do:

CPacket cpacket;
recv(cpacket.raw_packet, sizeof(packet_t), ...);

Voila, no reinterpret_cast with all its aliasing issues any more.

It no more defined than a reinterpret_cast tho. Writing to one
member of a union and reading it from another is undefined. Now I am not
sure if char makes an exception there, but I could not find anything ..
so I guess it is undefined as well.

That said, I suggest to prefer a solution involving a cast as to
explicitly show that you are doing something fishy there.

regards
 
L

leifer

From benben:
CPacket(packet_t p):i(p.i), c(p.c){}

I wish it were so easy. The packet example I have only has 2 member
variables, however the actual packet structure I have is crazy complex
composed of all kinds of types including structs inside of structs,
etc. I'd rather not redefine the entire packet layout again in the
class.
From Me:
That's a pretty good solution. Are you just too lazy to type m_packet.c
instead of c?

I'm confused by this statement...what is m_packet.c vs. c ?
 
M

Markus Schoder

Jakob said:
It no more defined than a reinterpret_cast tho. Writing to one
member of a union and reading it from another is undefined. Now I am not
sure if char makes an exception there, but I could not find anything ..
so I guess it is undefined as well.

union certainly gets around the alignment and aliasing issues of
reinterpret_cast. That said I think char * is excepted from aliasing
rules alignment is still an issue though.
That said, I suggest to prefer a solution involving a cast as to
explicitly show that you are doing something fishy there.

But you have to very carefully think about alignment and aliasing then.
Union was among other things meant to solve these problems.
 
R

Rolf Magnus

Noah said:
It is not safe to cast between unrelated types.

Actually, it is, if both are POD struct/class and are layout compatible.
However, that's not the case here, since a class with virtual functions is
not POD.
 
J

Jakob Bieling

Markus Schoder said:
Jakob Bieling wrote:
union certainly gets around the alignment and aliasing issues of
reinterpret_cast. That said I think char * is excepted from aliasing
rules alignment is still an issue though.

I was not specifically talking about aliasiang or alignment. Rather
was I referring to the general undefined-ness of using a union that way.
See 9.5.
But you have to very carefully think about alignment and aliasing
then. Union was among other things meant to solve these problems.

I might be missing something, in which case please refer me to the
appropriate part in the Standard. But from what I found, it is not
defined, thus not meant to solve this problem.

regards
 
M

Markus Schoder

Jakob said:
I was not specifically talking about aliasiang or alignment. Rather
was I referring to the general undefined-ness of using a union that way.
See 9.5.

I cannot see from 9.5 that my example leads to undefined behaviour. Why
do you think so?

9.5 however does clarify that unions guarantee correctly aligned memory
layout: "Each data member is allocated as if it were the sole member of
a struct."

3.10 (15) Furthermore resolves aliasing issues for unions (it also
mentions that they are not an issue with char anyway).
 
J

Jakob Bieling

Markus Schoder said:
Jakob Bieling wrote:
I cannot see from 9.5 that my example leads to undefined behaviour.
Why do you think so?

Read the first sentence and the note following it:

"In a union, at most one of the data members can be active at any time,
that is, the value of at most one of the data members can be stored in a
union at any time. [Note: one special guarantee is made in order to
simplify the use of unions: If a POD-union contains several POD-structs
that share a common initial sequence (9.2), and if an object of this
POD-union type contains one of the POD-structs, it is permitted to
inspect the common initial sequence of any of POD-struct members; see
9.2. ]"

There it is explicitly defined what you *can* do. You are not doing
any of this, thus you are relying on stuff that was not defined.

regards
 
N

Noah Roberts

Markus said:
Jakob Bieling wrote:

I cannot see from 9.5 that my example leads to undefined behaviour. Why
do you think so?

I think it is more implementation defined, no?
9.5 however does clarify that unions guarantee correctly aligned memory
layout: "Each data member is allocated as if it were the sole member of
a struct."

3.10 (15) Furthermore resolves aliasing issues for unions (it also
mentions that they are not an issue with char anyway).

Everything in a union /starts/ at the same place and is no different
in/out of the union, but that doesn't fix the other problems...such as
padding in the struct. However, if the data on both sides looks
exactly the same then we can expect the correct result on the other
side. But there is no guarantee that they are...packet_t could be
different on one side vs. the other...especially if being sent from a
machine with a different way of byte allignment in ints.

I don't know what aliasing issues you are refering to. I think both
methods work under different situations and neither is perfect as there
are undefined or implementation defined issues in both answers. Using
a union can be quite clean if you know certain things ahead of time and
can be pretty sure none of the variants can occur...copying the values
individually using reinterpret_cast on both sides can have less padding
issues...really, the correct answer is going to be more complex than
either and requires explicit alignment of the data packet in a
guaranteed fassion. The union method does what the OP was doing in the
first place in a cleaner way...
 
M

Markus Schoder

Jakob said:
Markus Schoder said:
Jakob Bieling wrote:
I cannot see from 9.5 that my example leads to undefined behaviour.
Why do you think so?

Read the first sentence and the note following it:

"In a union, at most one of the data members can be active at any time,
that is, the value of at most one of the data members can be stored in a
union at any time [...]."

There it is explicitly defined what you *can* do. You are not doing
any of this, thus you are relying on stuff that was not defined.

According to your interpretation to make another data member the active
one it is necessary to store to it if I get you right. Maybe that is
meant by 9.5 but I would not consider it as clear cut as you make it.
 
J

Jakob Bieling

Markus Schoder said:
Jakob Bieling wrote:

[snippet the quote a bit]
Rather was I referring to the general undefined-ness of using a
union that way. See 9.5.
I cannot see from 9.5 that my example leads to undefined behaviour.
Why do you think so?
Read the first sentence and the note following it:
"In a union, at most one of the data members can be active at any
time, that is, the value of at most one of the data members can be
stored in a union at any time [...]."
There it is explicitly defined what you *can* do. You are not
doing any of this, thus you are relying on stuff that was not
defined.
According to your interpretation to make another data member the
active one it is necessary to store to it if I get you right. Maybe
that is meant by 9.5 but I would not consider it as clear cut as you
make it.

I agree that this part by itself is pretty unclear. I take the
clarity from the additional wording in the note:

"it is permitted to inspect the common initial sequence of any of
POD-struct members"

The fact that they explicitly name 'inspecting' as valid for this
*special* case, implies that it is not valid for any other case.

This is to some extent subject to interpretation as well, I agree.
But it is probably also enough reason not to rely on the behaviour.

I have to revise my original position, though. Instead of using
reinterpret_cast (or any other built-in cast, for that matter), using
memcpy, as Noah suggested earlier, seems to be most portable. The
Standard defines what happens when memcpy'ing data from a POD and back,
leaving only the differences of the platforms to worry about. The OP can
wrap this up in a seperate little deserialize function to keep code
clean and readable.

regards
 
M

Markus Schoder

Jakob said:
Markus Schoder said:
Jakob Bieling wrote:

[snippet the quote a bit]
Rather was I referring to the general undefined-ness of using a
union that way. See 9.5.
I cannot see from 9.5 that my example leads to undefined behaviour.
Why do you think so?
Read the first sentence and the note following it:
"In a union, at most one of the data members can be active at any
time, that is, the value of at most one of the data members can be
stored in a union at any time [...]."
There it is explicitly defined what you *can* do. You are not
doing any of this, thus you are relying on stuff that was not
defined.
According to your interpretation to make another data member the
active one it is necessary to store to it if I get you right. Maybe
that is meant by 9.5 but I would not consider it as clear cut as you
make it.

I agree that this part by itself is pretty unclear. I take the
clarity from the additional wording in the note:

"it is permitted to inspect the common initial sequence of any of
POD-struct members"

The fact that they explicitly name 'inspecting' as valid for this
*special* case, implies that it is not valid for any other case.

Layout compatible POD-structs guarantee that you get the same value
member by member which is quite a bit more than we need here.
This is to some extent subject to interpretation as well, I agree.
But it is probably also enough reason not to rely on the behaviour.

Sad but true.
I have to revise my original position, though. Instead of using
reinterpret_cast (or any other built-in cast, for that matter), using
memcpy, as Noah suggested earlier, seems to be most portable. The
Standard defines what happens when memcpy'ing data from a POD and back,
leaving only the differences of the platforms to worry about. The OP can
wrap this up in a seperate little deserialize function to keep code
clean and readable.

For memcpy you need reinterpret_cast as well, no? Which means you can
just as well recv directly into your packet POD saving one copy.
Actually I think that should be fine as aliasing is not a problem with
char *. Platform issues still apply of course.
 

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

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top