Sign in to follow this  
NUCLEAR RABBIT

Struct Inheritance in C

Recommended Posts

NUCLEAR RABBIT    318

Hello,

 

I am trying to make a struct type and make another struct built on top of the other struct and I got it working, but I'm not sure what the tutorial means when it says I need to do pointer casting. I know how, but not sure why I have to do this? I seem to have it going without the casting so I'm not sure why they mentioned it. Can anyone help me understand why? rolleyes.gif

 

my code:

#include <stdio.h>
#include <string.h>

typedef struct {
    float wage;
    char name[15];
} Employee;

typedef struct{
    Employee super;
    char title[15];
} Manager;

int main(int argc, const char * argv[])
{
    Manager manager1;
    strcpy(manager1.super.name, "Steve");
    strcpy(manager1.title, "Manager On Duty");
    
    Manager manager2;
    strcpy(manager2.super.name, "Jack");
    strcpy(manager2.title, "CEO");
    
    printf("Title:\t\t\t\tEmployee Name:\n%s\t\t%s\n", manager1.title, manager1.super.name);
    printf("%s\t\t\t\t\t%s\n", manager2.title, manager2.super.name);
    
    return 0;
}

tutorial code: (not same example)

typedef struct
{
    // base members

} Base;

typedef struct
{
    Base base;

    // derived members

} Derived;

then they say "As Derived starts with a copy of Base, you can do this:"

Base *b = (Base *b)d;

"Where d is an instance of Derived. So they are kind of polymorphic. But having virtual methods is another challenge - to do that, you'd need to have the equivalent of a vtable pointer in Base, containing function pointers to functions that accept Base as their first argument (which you could name this)"

Edited by NUCLEAR RABBIT

Share this post


Link to post
Share on other sites
NUCLEAR RABBIT    318

What you have done is called "composition", not "inheritance". The good news is that composition is the right tool to use more often than inheritance.

The pointer cast is what makes inheritance look like inheritance: Some other part of the code can take a pointer to an Employee and do something with it, and if instead of giving it a pointer to an Employee you give it a pointer to a Manager (after casting), it would work just fine.

However, the main reason to use inheritance is not to build neat little hierarchies the way most OOP texts seem to suggest: The main reason is polymorphism, which means you can call a function that takes a pointer to Employee as an argument, and it might do something different depending on the specific type of employee that was passed. In C you would achieve this by making `Employee' have a function pointer as part of its data, so each derived type can provide an appropriate function to call there. (The mechanism that C++ uses is not exactly this; C++ compilers typically use a vtable instead (look it up).)

 

Sweet, thank you! Makes more sense now. And I'll will look into vtables 

Share this post


Link to post
Share on other sites
NUCLEAR RABBIT    318

The idea is that you'll be able to access and use a Manager as an Employee when you want to treat them as just another employee. For example, if you just wanted to do payroll, you don't care who's a manager or what their title is.

 

So you can create a function, for example, that takes an employee by pointer and prints out the payroll check, and you can pass it pointers to regular employees or pointers to managers by casting them as pointers to employees. The one function will handle both -- If you continue to access a Manager's Employee attributes through the Super member variable as you're doing in your code, then you'd need to separate functions.

 

You'd be doing functionally the same thing by passing the address of a manager's 'super' member variable -- but because its the first struct element, simply casting the Manager pointer to an Employee pointer achieves the same thing. All it says is "Treat this manager as a regular employee."

 

You're awesome!

Share this post


Link to post
Share on other sites
SeanMiddleditch    17565

The idea is that you'll be able to access and use a Manager as an Employee when you want to treat them as just another employee. For example, if you just wanted to do payroll, you don't care who's a manager or what their title is.

 

It's very difficult for us humans to properly explain inheritance and polymorphism without resorting to some kind of taxonomic metaphor, isn't it? smile.png

 

Java-like object models teach you that you need inheritance for generic programming. If you want to have a list of all Employees and then call some function Payroll on the whole list you must clearly ensure that all the Employees (even if different) share some common base in order to enable a common interface, right?

 

Functional approaches and those enabled with more sophisticated C++ idioms don't need any kind of is-a relationship between objects to enable a common interface to be applied to them.  Consider free (non-member) operator<< overloads for various types; you don't need to modify either std::ostream or your custom type in order to allow the custom type to be streamed.  All a Manager aggregating an Employee needs to work in a generic payroll system is something that knows how to extract the Employee field from a Manager.  It could also be done with adapter classes or several other approaches; which is best depends on the situation and architecture of the rest of the system.

 

C doesn't easily afford this kind of design, unfortunately, since it lacks tools like overloading or generics, but there's a reason so many non-trivial applications and frameworks are in C++ instead of C these days. :)

Share this post


Link to post
Share on other sites
Buckeye    10747

strcpy(manager1.title, "Manager On Duty");

Nothing to do with your post, but FYI, you should use strcpy_s for fixed character buffers. You reserved 15 characters in the struct. "Manager On Duty" has 15 characters and a terminating null. That needs a 16 character buffer. You're asking for problems if you ignore possibilities like that.

 

EDIT: from the docs for:

char *strcpy( char *strDestination, const char *strSource );

"The strcpy function copies strSource, including the terminating null character, to the location that's specified by strDestination.

Security Note

Because strcpy does not check for sufficient space in strDestination before it copies strSource, it is a potential cause of buffer overruns. Therefore, we recommend that you use strcpy_s instead."

Edited by Buckeye

Share this post


Link to post
Share on other sites
Buckeye    10747


strcpy_s is not a part of the C standard language. It is a Win32 specific function.

Good point.

 

Using std::string for names, titles, etc., would obviate that problem.

Share this post


Link to post
Share on other sites
BitMaster    8651

Using std::string for names, titles, etc., would obviate that problem.


Considering the whole thread is about doing the work in C that is not an option at all.

Share this post


Link to post
Share on other sites
LennyLen    5715

 

Using std::string for names, titles, etc., would obviate that problem.


Considering the whole thread is about doing the work in C that is not an option at all.

 

 

I wish I had a dollar for every time I've seen someone be told to use a C++ feature to solve a C issue.

Share this post


Link to post
Share on other sites
kop0113    2453

typedef struct{
    Employee super;
    char title[15];
} Manager;

 

This may look like composition (and can be) but it is also the start of inheritance in C.

 

Now that you have a struct of Employee (not a pointer) in the first bytes of the Manager struct, this struct can now be passed into any function accepting an Employee and it will work.

Prototype...
void employee_set_name(Employee* employee, char* name).
 
i.e.
employee_set_name(employee, "Fred");
employee_set_name((Employee*)manager, "Harry");

A diagram showing the "virtual" blocks of memory of the struct might be useful here and really it comes down to functions seeing the Manager struct effectively being an Employee struct "with a bunch of trailing bits on the end".

 

The simplicity of how this works is quite cool and I believe it is with the addition of an offset is how C++ supports the rare feature of multiple inheritance.

Edited by Karsten_

Share this post


Link to post
Share on other sites
Alessio1989    4634

 


Nothing to do with your post, but FYI, you should use strcpy_s for fixed character buffers.

 

strcpy_s is not a part of the C standard language.  It is a Win32 specific function.

 

See https://stackoverflow.com/questions/4570147/safe-string-functions-in-mac-os-x-and-linux for some suggestions on alternatives for other platforms.  There is no cross-platform solution that doesn't require using some third-party library, unfortunately.

 

 

C11 adds strcpy_s (and other "_s" safe functions). Before C11 they were part of some TR... Here's a link to the final C11 draft http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

Share this post


Link to post
Share on other sites
KnolanCross    1974

Unless you have dire performance needs use snprintf to manipulate string, it is the best all around function.

 

On topic, most times I have seen heritage system its was like this:

#include <stdio.h>

#define DEFAULT_BUFFER_SIZE 64

struct Employee_t {
    char name[DEFAULT_BUFFER_SIZE];
    float wage;
};

typedef struct Employee_t Employee;

struct Manager_t {
    Employee super;
    char title[DEFAULT_BUFFER_SIZE];
};

typedef struct Manager_t Manager;

struct Intern_t {
    Employee super;
    char college[DEFAULT_BUFFER_SIZE];
};

typedef struct Intern_t Intern;

static void printInfo(void* toPrint){

    Employee* casted = (Employee*)toPrint;
    printf("Employee %s wage is %.2f.\n", casted->name, casted->wage);
}

int main(int argc, char* argv[]){

    (void)argc;
    (void)argv;

    Manager first, second;
    Intern third;

    snprintf(first.super.name, sizeof(first.super.name), "Mike");
    first.super.wage = 10000;
    snprintf(first.title, sizeof(first.title), "CEO");

    snprintf(second.super.name, sizeof(second.super.name), "Jack");
    second.super.wage = 6000;
    snprintf(second.title, sizeof(second.title), "Day Shift Manager");

    snprintf(third.super.name, sizeof(third.super.name), "Steve");
    third.super.wage = 700;
    snprintf(third.college, sizeof(third.college), "MIT");

    printInfo(&first);
    printInfo(&second);
    printInfo(&third);

    return 0;

}

The main point here is to take advantage of the fact that both Manager and Intern structs have their Employee struct as their first element, meaning they will in the first bytes of the scruct. This allows you to directly cast them to the parent struct and use it as the parent.

 

Personally I have seem many codes that do that, specially when they are handling with generic containers. I have used it myself while using ORX engine, so all my structs had a parent struct with pointers to functions. This was really useful to free several different structs.

 

EDIT:

 

I used a void* in this example just to explicit show the cast, it could receive an Employee* instead.

Edited by KnolanCross

Share this post


Link to post
Share on other sites

 


He has #include at the top of the file. I made the assumption his "c programming" tag was incorrect.

 

Actually, he has #include <string.h>  ;)

 

 

<string.h> is a C header. It's called <cstring> in C++ and it dumps stuff in namespace std.

<string> is the C++ header.

Edited by Paradigm Shifter

Share this post


Link to post
Share on other sites
BeerNutts    4400

Unless you have dire performance needs use snprintf to manipulate string, it is the best all around function.

 

On topic, most times I have seen heritage system its was like this:

#include <stdio.h>

#define DEFAULT_BUFFER_SIZE 64

struct Employee_t {
    char name[DEFAULT_BUFFER_SIZE];
    float wage;
};

typedef struct Employee_t Employee;

struct Manager_t {
    Employee super;
    char title[DEFAULT_BUFFER_SIZE];
};

typedef struct Manager_t Manager;

struct Intern_t {
    Employee super;
    char college[DEFAULT_BUFFER_SIZE];
};

typedef struct Intern_t Intern;

...

...

 

 

Or even better:

typedef struct {
char name[DEFAULT_BUFFER_SIZE];
float wage;
} Employee_t;

typedef struct {
Employee_t super;
char title[DEFAULT_BUFFER_SIZE];
} Manager_t;

typedef struct {
Employee_t super;
char college[DEFAULT_BUFFER_SIZE];
} Intern_t;
 

No need to separate out the typedef and struct definitions in C, and, personally, if you're going to typedef something, it should have the '_t' modifier, not the struct definition that isn't even used.

Share this post


Link to post
Share on other sites
KnolanCross    1974

 

Unless you have dire performance needs use snprintf to manipulate string, it is the best all around function.

 

On topic, most times I have seen heritage system its was like this:

#include <stdio.h>

#define DEFAULT_BUFFER_SIZE 64

struct Employee_t {
    char name[DEFAULT_BUFFER_SIZE];
    float wage;
};

typedef struct Employee_t Employee;

struct Manager_t {
    Employee super;
    char title[DEFAULT_BUFFER_SIZE];
};

typedef struct Manager_t Manager;

struct Intern_t {
    Employee super;
    char college[DEFAULT_BUFFER_SIZE];
};

typedef struct Intern_t Intern;

...

...

 

 

Or even better:

typedef struct {
char name[DEFAULT_BUFFER_SIZE];
float wage;
} Employee_t;

typedef struct {
Employee_t super;
char title[DEFAULT_BUFFER_SIZE];
} Manager_t;

typedef struct {
Employee_t super;
char college[DEFAULT_BUFFER_SIZE];
} Intern_t;
 

No need to separate out the typedef and struct definitions in C, and, personally, if you're going to typedef something, it should have the '_t' modifier, not the struct definition that isn't even used.

 

 

Can you point me any advantage other than coding style? I like to split the declarations from the typedef, I find that it has better reabability when the structs are bigger.

Edited by KnolanCross

Share this post


Link to post
Share on other sites
Bacterius    13165


No need to separate out the typedef and struct definitions in C, and, personally, if you're going to typedef something, it should have the '_t' modifier, not the struct definition that isn't even used.

 

Technically the _t suffix is reserved by POSIX to declare new types in the future. But then, POSIX also forbids using functions that begin in "to" and "at" so... ph34r.png

Share this post


Link to post
Share on other sites
BeerNutts    4400

 

 

Unless you have dire performance needs use snprintf to manipulate string, it is the best all around function.

 

On topic, most times I have seen heritage system its was like this:

#include <stdio.h>

#define DEFAULT_BUFFER_SIZE 64

struct Employee_t {
    char name[DEFAULT_BUFFER_SIZE];
    float wage;
};

typedef struct Employee_t Employee;

struct Manager_t {
    Employee super;
    char title[DEFAULT_BUFFER_SIZE];
};

typedef struct Manager_t Manager;

struct Intern_t {
    Employee super;
    char college[DEFAULT_BUFFER_SIZE];
};

typedef struct Intern_t Intern;

...

...

 

 

Or even better:

typedef struct {
char name[DEFAULT_BUFFER_SIZE];
float wage;
} Employee_t;

typedef struct {
Employee_t super;
char title[DEFAULT_BUFFER_SIZE];
} Manager_t;

typedef struct {
Employee_t super;
char college[DEFAULT_BUFFER_SIZE];
} Intern_t;
 

No need to separate out the typedef and struct definitions in C, and, personally, if you're going to typedef something, it should have the '_t' modifier, not the struct definition that isn't even used.

 

 

Can you point me any advantage other than coding style? I like to split the declarations from the typedef, I find that it has better reabability when the structs are bigger.

 

What's the point of defining the name of the structure if you're not going to use it?  If you have a pointer in the struct point to itself (Link list), then it makes sense.  If you're not going to typedef it, then it makes a little sense.  But, if you give the struct a name and define a typedef for it, I don't see the point.  That's just me.

 

Also, you've named the structure with *_t...for what purpose?  I would've thought the _t defines a type.  If you're going to name the struct, using _s would've made more sense in this case.

 

Personally, I precede my typedef's with a "T", but it just coding style (and, POSIX doesn't have reign over how people choose to define their types). 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this