0

I am a C beginner with quite a lot of OOP experience (C#) and I am having trouble understanding how some notion of "polymorphism" can be achieved in C.

Right now, I am thinking how to capture the logical structure of a file system using structs. I have a folder that contains both folders and files. Folders in this folder can contain another files and folders, etc.

My approach:

typedef enum { file, folder } node_type;

struct node;
typedef struct {
    node_type type;
    char *name;
    struct node *next;
    struct node *children;
} node;

Is this the best I can do? I have found a lot of posts on "polymorphism in C", but I would like to see how a polymorphic data structure like this can be built cleanly and efficiently (in terms of memory wasted on unused members of those structures).

Thanks.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
David
  • 1,055
  • 8
  • 23
  • 1
    You haven't shown what you're trying to achieve. Are you supposed to have two subclasses of a single base class? Do you need virtual method dispatch? How do you want to use them? – Useless Aug 09 '13 at 15:38
  • @Useless I would like to have in-memory representation of the file system hierarchy that can easily be traversed. If I did this in C, I would probably create a Node abstract class that would hold the name and two descendant classes File and Folder. Folder would add a `List children`. – David Aug 09 '13 at 15:43
  • Look at the Union statement/construct in C. – JackCColeman Aug 09 '13 at 15:58
  • So after all that the fundamental problem is you want both folders and files represented by the same node layout, where folders need a child (subfolders and contained files) and a sibling pointer list, while files just need the sibling list. Does that about sum it up? – WhozCraig Aug 09 '13 at 16:01
  • @WhozCraig Not necessarily. The idea to define "one struct to hold it all" is the only one that came to my mind. My question is, is there a better way? – David Aug 09 '13 at 16:04
  • 1
    In Unix there is a standard representation for directory entries called "dirent". It looks much as above, with a discriminator field to indicate what type of file is being referenced: http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man5/dirent.5.html – Ross Bencina Aug 09 '13 at 16:30
  • http://stackoverflow.com/a/12974623/1673391 – Grijesh Chauhan Aug 09 '13 at 16:50

3 Answers3

2

I hope I understand what you want - I'm unsure but I guess you want to do something like that:

typedef struct
{
   int type; // file or folder?
} Item;

typedef struct
{
   struct A;
   // data related to a file
} File;

typedef struct
{
   struct A;
   // data related to a folder - like pointer to list of Item
} Folder;

As long as both structure follow the same memory mapping (same variables) and adds to it as a child, you'll be able to use the pointer properly in both structs.

Check this one out as well: How can I simulate OO-style polymorphism in C?

Edit: I'm not sure about the syntax above (took it from the link above). I'm used to writing it this way instead:

typedef struct
{
  int type;
  // data for file
} File;

typedef struct
{
  int type;
  // data for folder - list, etc
} Folder;
Community
  • 1
  • 1
MasterPlanMan
  • 992
  • 7
  • 14
  • do you mean `Item A` instead of `struct A`? – Ingo Leonhardt Aug 09 '13 at 16:00
  • Thanks for interesting reading. Is this common to C programs? – David Aug 09 '13 at 16:01
  • @David "is it common?" it is used, but more common is "Abstract Data Type" programming where you use a struct to define a single (non-polymorphic) data type and code a bunch of methods against it. One example of this kind of "struct embedding inheritance" is the BSD sockets type sockaddr. This is like the base type, then sockaddr_in provides fields for representing internet addresses. Most sockets functions are polymorphic in that they can deal with different types of sockaddr including sockaddr_in. – Ross Bencina Aug 09 '13 at 16:23
  • @RossBencina Is my understanding correct that pointer to a struct is actually pointer to its first member? – David Aug 09 '13 at 16:27
  • @David http://stackoverflow.com/questions/7312555/in-c-does-a-pointer-to-a-structure-always-point-to-its-first-member – Ross Bencina Aug 10 '13 at 04:01
2

C has no intrinsic notion of polymorphism.

You will end up implementing the mechanisms that you want from scratch. That's not a bad thing. It gives you a lot more flexibility. For example, C++ virtual methods are hard-wired per class, you can't change method pointers per-instance.

Here are a few ideas:

Your node_type field provides a way to do a runtime type query. Going further, you can pack multiple types into one struct using a discriminated (or tagged) union: http://en.wikipedia.org/wiki/Tagged_union. I'm not sure whether a variant type qualifies as OO though.

Polymorphism is usually about behavior. You could store function pointers ("methods") in the struct, with pointers to different functions providing different behavior for different object instances. The C++ way of doing things is for each class to have a table of function pointers, then each object instance references the table for its class (incidentally the table pointers can also play the role of your node_type for RTTI). This is called a virtual method table.

Data inheritance means that subclasses contain all of the base class' data members plus some extra stuff. In C the easiest way to do this is by embedding the base class struct at the head of the derived class struct. That way a pointer to derived is a pointer to base.

typedef struct BaseClass {
  int baseMember;
} BaseClass;

typedef struct DerivedClass {
  BaseClass base;
  int derivedMember;
} DerivedClass;

You could do worse than read "Inside the C++ Object Model" by Stanley B. Lippman. For example, this will help if you want to get an idea of how to implement multiple inheritance.

Ross Bencina
  • 3,822
  • 1
  • 19
  • 33
1

Here's an illustration of old-school C polymorphism, based on ancient memories of X/Motif.

If you just want a discriminated union (or even just a typed structure with a child pointer that may be null), it's probably simpler in your case.

enum NodeType { TFile, TFolder };
struct Node {
    enum NodeType type;
    const char *name;
    struct Node *next;
};

struct FileNode {
    struct Node base_;
};

struct FolderNode {
    struct Node base_;
    struct Node *children;
    /* assuming children are linked with their next pointers ... */
};

Here are the constructors - I'll leave populating the linked lists as an exercise for the reader ...

struct Node* create_file(const char *name) {
    struct FileNode *file = malloc(sizeof(*file));
    file->base_.type = TFile;
    file->base_.name = name; /* strdup? */
    file->base_.next = NULL;
    return &file->base_;
}

struct Node* create_folder(const char *name) {
    struct FolderNode *folder = malloc(sizeof(*folder));
    folder->base_.type = TFolder;
    folder->base_.name = name;
    folder->base_.next = NULL;
    folder->children = NULL;
    return &folder->base_;
}

Now we can walk a hierarchy, checking the type of each node and responding appropriately. This relies on the first member subobject having zero offset to the parent - if that doesn't hold (or you need multiple inheritance), you have to use offsetof to convert between base and "derived" types.

void walk(struct Node *root,
          void (*on_file)(struct FileNode *),
          void (*on_folder)(struct FolderNode *))
{
    struct Node *cur = root;
    struct FileNode *file;
    struct FolderNode *folder;

    for (; cur != NULL; cur = cur->next) {
        switch (cur->type) {
        case TFile:
            file = (struct FileNode *)cur;
            on_file(file);
            break;
        case TFolder:
            folder = (struct FolderNode *)cur;
            on_folder(folder);
            walk(folder->children, on_file, on_folder);
            break;
        }
    }
}

Note that we have a sort-of-polymorphic base type, but instead of switching on the type enumeration we could have a more completely polymorphic setup with virtual functions. Just add a function pointer to Node, something like:

void (*visit)(struct Node *self,
             void (*on_file)(struct FileNode *),
             void (*on_folder)(struct FolderNode *));

and have create_file and create_folder set it to an appropriate function (say, visit_file or visit_folder). Then, instead of switching on the enumerated type, walk would just call

cur->visit(cur, on_file, on_folder);
Useless
  • 64,155
  • 6
  • 88
  • 132