Derived classes in C - What is your favorite metho

2020-02-10 09:20发布

In my experience in object oriented C programming I have seen two ways to implement derived classes.


First Method, have a definition of the parent class as a .h file. Then each class that derives from this class will have do:

File parent_class.h:

int member1;
int member2;

File testing.c:

struct parent_class {
    #include "parent_class.h" // must be first in the struct
}

struct my_derived_class {
    #include "parent_class.h" // must be first in the struct
    int member3;
    int member4;
}

Second Method, would do:

File testing.c:

struct parent_class {
    int member3;
    int member4;
}

struct my_derived_class {
    struct parent_class; // must be first in the struct
    int member3;
    int member4;
}

What is your favorite method of doing derived classes in C ( doesn't have to be what I have done )? and why?

Which method would you prefer, first or second method ( or your own )?

标签: c oop
9条回答
别忘想泡老子
2楼-- · 2020-02-10 09:53

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.

查看更多
家丑人穷心不美
3楼-- · 2020-02-10 09:56

The code I worked with used the first method.

The only two reasons I can think of for using the first method is:

  1. Saves you some cycles because you will be doing one less de-referencing
  2. When you cast the derived class pointer to the parent class, i.e. (struct parent_class *)ptr_my_derived_class , you know 100% what the expected result will be.

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.

查看更多
叛逆
4楼-- · 2020-02-10 09:57

I have used method #2 before and found it works quite ok:

  • you can upcast to the base type anytime if it is the first member in derived type
  • instead of dereferencing all the time to get at base members, just keep two pointers: one for the base interface, one for the derived interface

free() on the pointer to the base structure will of course also free up the derived fields, so that isn't an issue...

Also, I find accessing base fields something I tend to do in a polymorphic situation: I only care about those fields in methods that care about the base type. Fields in the derived type are used in methods only interested in the derived type.

查看更多
手持菜刀,她持情操
5楼-- · 2020-02-10 10:01

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.

查看更多
叛逆
6楼-- · 2020-02-10 10:03

I'm one of the maintainers of a library that uses method 2. Works just as well as method 1, but without any preprocessor trickery. Or it actually works better, since you can have functions that take the base class as argument and you can just cast to the base struct, C guarantees that this works for the first member.

The more interesting question is, how do you do virtual functions? In our case, the struct has pointers to all the functions, and the initialization set them up. It's slightly simpler, but has more space overhead than the "proper way" with a pointer to a shared vtable.

Anyway, I'd prefer to use C++ rather than kludge it with plain C, but politics..

查看更多
爷、活的狠高调
7楼-- · 2020-02-10 10:06

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.

查看更多
登录 后发表回答