In my experience in object oriented C programming I have seen two ways to implement derived classes.
First Method, have a definition of the pa
I prefer the first method because you can cast the derived class pointer to the parent class without any worries.
It's the other way round.
The C standard guarantees that the address of a struct is the address of the first member, so in the second case it is safe to cast a pointer to derived to parent, as the first member of derived is the parent struct, and a the struct as a member as the same layout as the same struct when not a member, so casting a pointer to a derived to parent will always work.
The same is not true for the second case. Two structs with some members defined as the same type may have different padding between those members.
It would be reasonable for a 64 bit bigendian compiler to compile
struct A { a uint64_t; b uint32_t; };
such that sizeof(A) is a whole multiple of 8 and b is 64 bit aligned, but compile
struct B { a uint64_t; b uint32_t; c uint32_t; };
so that sizeof(B) is a whole multiple of 8, but b is only 32 bit aligned so that it doesn't waste space.
The code I worked with used the first method.
The only two reasons I can think of for using the first method is:
I prefer the first method because you can cast the derived class pointer to the parent class without any worries.
Can you do the same thing with the second method? ( It seems like you would have to jump through a hoop or two to get the same end result )
If you can do the same with method 2, then I think both methods would be equal.
I know that GNOME uses the 2nd method, and casting pointers was a known thing as well. I don't remember there being any real hoops to jump through to do so. In fact, From a C memory model standpoint, there can't be any semantic difference between the two, since AFAIK the only possible difference would be compiler differences in structure padding, but since the code all runs through the same compiler, that would be a moot point.
The first method is hideous and it hides important information. I'd never use it or allow it being used. Even using a macro would be better:
#define BODY int member1; \
int member2;
struct base_class
{
BODY
};
But method 2 is much better, for reasons others have pointed out.
Second option forces you to write very long names like myobj.parent.grandparent.attribute
, which is ugly. First option is better from syntax point of view, but it is a bit risky to cast child to parent - I'm not sure whether is is guaranteed by standard that different structs will have same offsets for similar members. I guess compiler may use different padding for such structs.
There is another option, if you are using GCC - anonymous struct members, which is part of MS extension, so I guess it was originated by some MS compiler and still may be supported by MS.
Declarations look like
struct shape {
double (*area)(struct shape *);
const char *name;
};
struct square {
struct shape; // anonymous member - MS extension
double side;
};
struct circle {
struct shape; // anonymous member - MS extension
double radius;
};
In your "constructor" function you need to specify correct function for calculating area and the enjoy the inheritance and polymorphism. The only problem that you always need to pass explicit this
- you cannot just call shape[i]->area()
.
shape[0] = (struct shape *)new_square(5);
shape[1] = (struct shape *)new_circle(5);
shape[2] = (struct shape *)new_square(3);
shape[3] = (struct shape *)new_circle(3);
for (i = 0; i < 4; i++)
printf("Shape %d (%s), area %f\n", i, shape[i]->name,
shape[i]->area(shape[i])); // have to pass explicit 'this'
Compile with gcc -fms-extensions
.
I never used it in real-life project but I tested it some time ago and it worked.
At a former job, we used a preprocessor to handle this. We declared classes using a simple C++-style syntax, and the preprocessor generated C headers that were basically equivalent to the First Method, but without the #includes. It also did cool things like generating vtables and macros for upcasting and downcasting.
Note that this was in the days before good C++ compilers existed for all the platforms we targeted. Now, doing this would be stupid.