Multiple structs, same fields that need to be accessed in a method

别说谁变了你拦得住时间么 提交于 2019-12-02 10:42:46

is it enough to "just" put them in the same order at the very begining?

Yes, if you're careful as you've done above.

also, how to design the method header to accept it?

There are different ways to do this.

Here's an example, using the [ugly] equivalent of a c++ "base" class:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
};

struct frame {
    struct geo geo;
};

struct button {
    struct geo geo;
    int updown;
};

struct whatever {
    struct geo geo;
    int what;
    int ever;
};

void
frame_render(struct geo *geo)
{
    struct frame *frm;
    struct button *but;
    struct whatever *what;

    switch (geo->type) {
    case FRAME:
        frm = (struct frame *) geo;
        frame_render_frame(frm);
        break;

    case BUTTON:
        but = (struct button *) geo;
        printf("x=%d y=%d updown=%d\n",geo->x,geo->y,but->updown);
        frame_render_button(but);
        break;

    case WHATEVER:
        what = (struct whatever *) geo;
        printf("x=%d y=%d what=%d ever=%d\n",
            what->geo.x,what->geo.y,what->what,what->ever);
        frame_render_whatever(what);
        break;
    }
}

Here's a way to use a virtual function table:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct geo;

// virtual function table
struct virtfnc {
    void (*calc)(struct geo *);
    void (*render)(struct geo *);
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
    struct virtfnc *fnc;
};

struct frame {
    struct geo geo;
};

struct button {
    struct geo geo;
    int updown;
};

struct whatever {
    struct geo geo;
    int what;
    int ever;
};

void
frame_render(struct geo *geo)
{
    struct frame *frm = (struct frame *) geo;

    // whatever
}

void
button_render(struct geo *geo)
{
    struct button *but = (struct button *) geo;

    // whatever
}

void
whatever_render(struct geo *geo)
{
    struct whatever *what = (struct whatever *) geo;

    // whatever
}

void
any_render(struct geo *geo)
{

    geo->fnc->render(geo);
}

Here's a third way that uses a union. It is simpler but requires that the base struct be as large as the largest sub-class:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct frame {
    ...
};

struct button {
    int updown;
};

struct whatever {
    int what;
    int ever;
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
    union {
        struct frame frame;
        struct button button;
        struct whatever what;
    } data;
};

void
any_render(struct geo *geo)
{

    switch (geo->type) {
    case FRAME:
        render_frame(&geo->data.frame);
        break;

    case BUTTON:
        render_button(&geo->data.button);
        break;

    case WHATEVER:
        render_whatever(&geo->data.what);
        break;
    }
}

UPDATE:

is this approach casting safe? eg. putting all into some array that is of the type frame* and then just accessing frame->geo? or would that cause any problems with later calls to free(..)?

No problem with free if allocations are done with the derived types (e.g. frame, button), but not the base type geo: malloc(sizeof(struct button)).

To have a simple array [of shapes], the union method would need to be used (i.e. all derived structs must have the same size). But, this would be wasteful if we had some subtype that used a lot more space than the others:

struct polyline {
    int num_points;
    int x[100];
    int y[100];
};

This could still be done with methods #1 or #2 [where the subtype structs are of different sizes] with an indirect pointer array:

void
all_render(struct geo **glist,int ngeo)
{

    for (;  ngeo > 0;  --ngeo, ++glist)
        any_render(*glist);
}

Rather than an array of different shapes, I'd consider a [doubly] linked list. This allows the subtype structs to have different sizes. We'd add a struct geo *next element to struct geo. Then, we could do:

void
all_render(struct geo *geo)
{

    for (;  geo != NULL;  geo = geo->next)
        any_render(geo);
}

The list approach may be preferable, particularly if we add/remove shapes on a dynamic basis [or reorder them based on Z-depth].

Or, some shapes might contain others. So, we could add struct geo *children to struct geo. Then, it's easy to (e.g.) draw a containing box, then all the shapes within that box by traversing the children list. If we go for children, we may as well add struct parent *parent as well so each shape knows what shape contains it.

If all of the structures start with members of the same types, in the same order, corresponding members will have the same offsets. Most compilers can be configured to allow a pointer of any structure type to be used to inspect members of the Common Initial Sequence of any other, but there are a few issues:

  1. On some unusual platforms, if an object is followed by padding bytes, an instruction that writes the object and padding bytes together (likely storing meaningless values in the latter) may be faster than instructions that write only the object. If a member is followed by a padding byte in some structures, but by meaningful data in another, a write to that member using the type where it's followed by padding bytes may write overwrite the "padding bytes" with meaningless values, thus corrupting the values of the following members in the other structure types. I am unaware of any architectures in current use where this would be an issue for any struct members other than bitfields, and I am unaware of any current implementations where this would be an issue even for those, but such possibilities could arise on some platforms, especially with bitfields.

  2. Given something like:

    int readField1OfS1(struct s1 *p) { return p->field1; }
    
    struct s2 *myStruct2Ptr;
    
    if (readField1ofS1((struct s1*)myStruct2Ptr)
       ...
    

    some compilers like gcc and clang won't reliably allow for the possibility that the value returned from the function might depend upon a value held by part of the Common Initial Sequence of an object of type struct s2 at the time of the call unless optimizations are disabled (e.g. using the -fno-strict-aliasing option). I would think that presence of a cast from struct s2* to struct s1* within the function call expression should allow a quality compiler to recognize that anything function might do something with an object of type struct s1 might be done on a struct s2, but since the Standard doesn't explicitly require that, the authors of gcc and clang refuse to make any effort to reliably recognize such constructs even in straightforward cases like the above.

Code which makes use of the Common Initial Sequence rule will work reliably on nearly any suitably-configured compiler, but some like gcc and clang must be specially configured using -fno-strict-aliasing option. The ability to exploit Common Initial Sequence guarantees has been a well-established part of the language since 1974, and when the Standard was written, anyone familiar with the language would have understood that it was designed to allow for constructs like those above, which compilers should have no difficulty recognizing. Since the authors of the Standard failed to explicitly require that the CIS guarantees be honored in useful fashion, however, the authors of clang and gcc have decided they'd rather claim that programs relying upon the decades-old CIS guarantees is "broken" than honor 40+ years of precedent.

The "classical" method is to have a struct that contains a union of all possible objects and an enum that identifies which exactly object has been passed:

struct renderable {
    enum {FRAME, BUTTON, WHATVERE, ...} type;
    union {
        struct frame frame;
        struct button button;
        struct whatever whatever;
        ....
    } data;
};

After passing this struct to the renderer, use a switch on the type field to extract the coordinates, etc:

void renderer (renderable *obj, ...) {
    ...
    switch(obj->type) {
        case FRAME: x = obj->data.frame.x; ...; break;
        case BUTTON: x = obj->data.button.x; ...; break;
        ...
    }
    ...
}

It was reportedly this kind of monstrosity that encouraged Stroustrup to invent C++ :)

Edited

Another "classical" solution is to have a separate struct that has the dimensions and the position of any object:

struct geometry {
    int x, y, width, height;
}

You can store this structure at the beginning of any object-specific struct and use a cast to gain access to it:

struct frame {
    struct geometry geo;
    // more stuff
};

struct frame frame = {....};
rendered((void*)&frame, ...);

In the renderer:

void renderer (void *obj, ...) {
    ...
    struct geometry *geo = (struct geometry *)obj;
    geo->x ...
}

The latter approach may be somewhat unsafe. To make it 100% safe, separate the geometry from the object-specific information and pass them to the renderer as two separate structs.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!