Struct Inheritance in C

Started by
20 comments, last by BeerNutts 10 years, 1 month ago

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)"

Advertisement
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).)

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

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."

throw table_exception("(? ???)? ? ???");

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!

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. :)

Sean Middleditch – Game Systems Engineer – Join my team!


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."

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.


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.

Sean Middleditch – Game Systems Engineer – Join my team!


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.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

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.

This topic is closed to new replies.

Advertisement