I'm doing inheritance ( i.e. calling super class's function with subclass data type) with C, but encountered the aliasing issue.
In below, shape
function is called with rectangle
object.
In the case of me->super
, the x
and y
are correct. However, they're wrong in that of (Shape*)me
.
The reason I prefer (Shape*)me
over me->super
is that I want to hide struct implementation from clients.
Shouldn't C standard guarantee that? As per Section 6.7.2.1.13
“… A pointer to a structure object, suitably converted, points to its initial member. There may be unnamed padding within a structure object, but not at its beginning”.
/*shape.h*/
#ifndef SHAPE_H
#define SHAPE_H
typedef struct Shape Shape;
Shape* Shape_ctor(int x, int y);
int Shape_getX(Shape* me);
int Shape_getY(Shape* me);
#endif
/*shape.c*/
#include <stdlib.h>
#include "shape.h"
struct Shape
{
int x;
int y;
};
Shape* Shape_ctor(int x, int y)
{
Shape* me = malloc(sizeof(struct Shape));
me->x = x;
me->y = y;
return me;
}
/*rectangle.c*/
#include <stdlib.h>
#include "rectangle.h"
#include "stdio.h"
struct Rectangle
{
Shape* super;
unsigned int width;
unsigned int height;
};
Rectangle* Rectangle_ctor(int x, int y, unsigned int width, unsigned int height)
{
Rectangle* me = malloc(sizeof(struct Rectangle));
me->super = Shape_ctor(x, y);
me->width = width;
me->height = height;
printf("x: %d\n", Shape_getX(me->super)); //correct value
printf("y: %d\n", Shape_getY(me->super)); //correct value
printf("x: %d\n", Shape_getX((Shape*)me)); // wrong value
printf("y: %d\n", Shape_getY((Shape*)me)); // wrong value
return me;
}
/*main.c*/
#include <stdio.h>
#include "rectangle.h"
int main(void) {
Rectangle* r1 = Rectangle_ctor(0, 2, 10, 15);
printf("r1: (x=%d, y=%d, width=%d, height=%d)\n", Shape_getX((Shape*)r1)
, Shape_getY((Shape*)r1)
, Rectangle_getWidth(r1)
, Rectangle_getHeight(r1));
return 0;
}
CodePudding user response:
You should place a base type as a first member. Not a pointer to the base type.
struct Rectangle {
Shape super;
...
}
Moreover, you should redesign Shape_ctor
. I suggest taking a pointer as a parameter and delegate the memory management to the caller.
Shape* Shape_ctor(Shape *me, int x, int y)
{
me->x = x;
me->y = y;
return me;
}
The constructor of rectangle would be:
Rectangle* Rectangle_ctor(Rectangle *me, int x, int y, unsigned int width, unsigned int height)
{
Shape_ctor(&me->super, x, y); // call base constructor
me->width = width;
me->height = height;
printf("x: %d\n", Shape_getX(&me->super)); //correct value
printf("y: %d\n", Shape_getY(&me->super)); //correct value
return me;
}
Typical usage:
Rectangle rect;
Rectangle_ctor(&rect, ...);
or a bit more exotic variants like:
Rectangle* rect = malloc(sizeof *rect);
Rectangle_ctor(rect, ...);
// or
Rectangle* rect = Rectangle_ctor(malloc(sizeof *rect), ...);
// or even kind of automatic pointer
Rectangle* rect = Rectangle_ctor(&(Rectangle){0}, ...);
The cast would only be needed for implementation of virtual methods like Shape_getArea()
.
struct Shape {
...
double (*getArea)(struct Shape*);
};
double Shape_getArea(Shape *me) {
return me->getArea(me);
}
double Rectangle_getArea(Shape *base) {
Rectangle *me = (Rectangle*)base; // the only cast
return (double)me->width * me->height;
}
Rectangle* Rectangle_ctor(Rectangle *me, int x, int y, unsigned int width, unsigned int height) {
...
me->super.getArea = Rectangle_getArea;
...
}
// usage:
Rectangle rect;
Rectangle_ctor(&rect, 0, 0, 3, 2);
Shape *shape = &rect.super;
Shape_getArea(shape); // should return 6
EDIT
In order to hide internals of Shape
place a pointer to its private data in the structure. Initialize this pointer with relevant data in Shape_ctor
.
struct Shape {
void *private_data;
// non private fields
};
CodePudding user response:
The initial member of Rectangle
is a Shape*
, not a Shape
. And the initial member of Shape
is an int, not a
Shape*`. The commonality you assume just isn't there.
If you look at how C implementations implement inheritance, in case of single inheritance they will place the base subobject at offset 0 inside the full object. Pointers to base class subobjects are used for virtual inheritance, but that rapidly gets complex.