tags:

views:

671

answers:

7

Let me first say that I've got a fair amount of experience in both C and C++. However, I'm starting a new project in C and I've been working in object-oriented languages for so long (C# and C++) that I am having trouble coming up with an effective way to encapsulate functionality in a procedural language. My first thought was to simply fall back on my OO knowledge and structure it something like:

struct Foo 
{
    int x;
    char *y;
}; 

struct Foo *new_Foo()
{
    return (struct Foo *)malloc(sizeof(struct Foo));
} 

void Foo_member_function(struct Foo *foo, int z)
{
    foo->x = z;    
}

But that just seems tedious and contrary to the spirit of C. Not to mention that it is a poor-man's OO.

This program is ultimately going to get fairly sizable, so starting from a good design organization is critical. I imagine with the years of development in C, certain design patterns have developed in how to best structure the code for maintainability. Much like functional programming, I'm hoping that procedural programming has a paradigm that is clean and fairly readable.

Pointers to relevant articles and books are acceptable as well.

+6  A: 

What you are suggesting is the way I always wrote C programs back in the days when I did such a thing. I don't think it is "poor mans OO", I think it is sensible procedural programming practice.

I would observe a couple of things about your C code:

  • use typedefs with struct definitions so you don't need to scatter the 'struct' keyword throughout the code
  • only use casts when they are actually needed - the cast on the return value from malloc() is unecessary
anon
Good tips. What I meant by "poor mans OO" was that it was a very limited OO (i.e. no polymorphism, no encapsulation, etc).
HVS
but do be aware that typedefs are considered bad practice to some, including the Linux kernal crew
temp2290
A: 

C has been a low-level language and in the respect it would be very useful to organize your data structures in accordance with your code functions and modules.

I would suggest that you use typedefs and enumerations wherever you would like to create data objects. Use macros or static functions to initialize, allocate and 'destroy' as required.

nik
+9  A: 

This is quite a normal and sensible practice. But try not to expose the struct layout in header files, so that you have some flexibility in how it's implemented and manage your dependencies better.

See Opaque pointer for more details.

Alaric
I did not know you could do that in C. That is very useful information.
HVS
reminds me of HWND, and native win32 C api
bobobobo
+2  A: 

Hmmm... We used to just use naming conventions... Ergo: str* does stuff with what common datastructure? So maybe just take the C# syntax and s/./_/g?

  • foo_constructor
  • foo_destructor
  • foo_someMethod
  • foo_someMethod2 // ain't no overloading in ANSI C
  • foo_otherMethod

... and there ain't no inheritance ...

  • foo2_constructor
  • foo2_destructor
  • foo2_someMethod // and there ain't no polymorphism

But look on the bright side... you can use pointer-to-pointer-to-pointer-to-function-returning-a-pointer-to-pointer-int! Oh the joy!

My bestest advise is to learn the lessons of Java (and by inference C#) and structure your libraries to NOT have side-effects... more typdefs == less headaches... and if your work-out how to follow this sage advise please let me know ;-)

Cheers. Keith.

corlettk
By "side effects", I assume you mean having static variables, globals, etc? Obviously the libraries would have side effects on the "objects" they modify.
HVS
Generally, you can return new state instead of mutating existing one in order to update some state, this is in line with functional programming and will make data flow dependencies much more explicit and easier to follow.
none
A: 

That's a pretty reasonable way to write a C program. There is another large application out there, which does pretty much the same stuff - called the Linux kernel. Some nearly OO-features used in there:

  • structs and operations on structs for encapsulation just like in your example
  • pointers to base structs as a form of poor man's inheritance -- you'll find loads of references to struct kobject in there
  • macros to generate functions as a replacement for template programming
BjoernD
A: 

I agree with the suggestions above. You are doing it the best way .. if you want to program in C.

Of course, you could write a pre-processor to automatically generate these declarations and things for you .. maybe use a "Class" declaration ... put the functions you want to be member functions inside the class .. etc.

But what we've got here is a simple C++ to C compiler. Why not just program in C++, use a real C++ compiler, use clean interfaces, and just link the C++ code with the C code? What is the reason that you need to code in C vs. C++ anyways? Or if you need to, generate C code from the compiler and compile the output C code together with whatever else you need.

Larry Watanabe
I'm really not a fan of C++, even though it has improved enormously over that past few years (Boost is a great library). That aside, part of this project is fairly low-level which is one of the reasons for choosing C. The other being that it will be open source and want to keep it as flexible as possible. I *could* be done in C++, but that would cause more problems that I feel it's worth.
HVS
You don't need to use all the features - just stick to a very simple subset - i.e. classes, methods, constructors, destructors - keep the rest in standard c. For open source releases, you could run the C++ to C compiler and distribute the C code.
Larry Watanabe
A: 

I have been working on a project for a little while where the library needs to be in C, but I want to have some form of OO functionality. I am doing something similar to this with a little more detail.

struct klass {
  char * value;

  void (*set_value) (struct klass *, const char *);
  void (*destroy) (struct klass *);
};

static void
klass_method_set_value (struct klass * k, const char * value) {
  if (k->value == NULL) {
  }
}

static void 
klass_object_desetroy (struct klass * k) {
  free (k);
  k = NULL;
}

static void
klass_method_destroy (struct klass * k) {
  klass_object_destroy (k);
}

static struct klass *
klass_object_init (void) {
  struct klass * obj = (struct klass *) malloc (sizeof (struct klass*) );

  /* members */
  obj->value = NULL;

  /* methods */
  obj->set_value = klass_method_set_value;
  obj->destroy = klass_method_destroy;
  return obj;
}

struct klass * 
klass_new (void) {
  return klass_object_init ();
}

Forgive me if something is wrong; wrote it a little quick.

John Bellone
Yeah, that's kind of what I wanted to avoid. I've heard that that is basically what Stroustrup coded the first C++ compiler to do, though.
HVS