3

I've been mulling this over in my head lately. Most of us are well aware that in C, in order to create a struct, you normally prefix it with a typedef to avoid calling the struct keyword before referencing the object. Of course, C is limited to structs rather than classes. To compensate for this, C tends to use global functions dedicated to a struct to create an object-oriented approach

For example:

typedef struct{
    int foo;
    float bar;
    char* baz;
} SomeStruct;

Vs.

struct AnotherStruct {
    int foo;
    float bar;
    char* baz;
};

AnotherStruct must have the prefix keyword struct before it, when an object of that type is declared within a function. For example:

int main( ... )
{
   struct AnotherStruct obj1; //correct
   SomeStruct obj2; //correct
   struct SomeStruct obj3; //bad
   AnotherStruct obj4; //bad 
}

In terms of the object-oriented approach:

typedef struct {
    //member variables here
} SomeStruct;

SomeStruct* SomeStruct_New( int a, int b, int c )
{
    SomeStruct* obj = calloc( sizeof( SomeStruct ), 1 ); //not sure if that's correct -- been a while since I've used calloc.
    obj.a = a;
    obj.b = b;
    obj.c = c;
    return obj;
}

void SomeStruct_Free( SomeStruct* free )
{
    free( free );
}

These functions are pretty easy to implement without the wrappers - I'm just using them for the sake of example. My point is that, given that you can already create a struct in C++ which doesn't require the typedef to declare without the struct keyword, and using non encapsulated functions which pertained to these structs for an object-oriented approach, I was curious to know if there are any advantages to the C approach of coding in C++, which would include using static global functions as private member functions, along with global function constructors which would return pointers to objects.

This is just mainly out of curiosity, as there are times where I feel like taking the C approach just for the sake of taking it, but this may just be a preferential thing.

zeboidlund
  • 9,731
  • 31
  • 118
  • 180
  • 1
    possible duplicate of [Does procedural programming have any advantages over OOP?](http://stackoverflow.com/questions/528234/does-procedural-programming-have-any-advantages-over-oop) – Caleb Feb 01 '12 at 08:12
  • 1
    I don't see your point. This is not and object oriented approach, maybe it is a good programming practice, but the struct has all the members public and you cannot have methods of course. In C++ you can use classes instead, and take advantage of constructors, a destructor, methods and most of all polymorphism! – vulkanino Feb 01 '12 at 08:19
  • 3
    How does `typedef struct Foo Foo` come into this? That construct exists simply to reduce the amount of typing needed, and does not have any bearing on procedural vs OOP, encapsulated vs not encapsulated or whatever your question is about. – Mankarse Feb 01 '12 at 08:20
  • @vulkanino When doing things like this in C, you typically declare the members as static, at file scope, to create private encapsulation. Which is an OO approach, even if isn't particularly elegant. – Lundin Feb 01 '12 at 08:25
  • @Lundin, of course, but I think this is not OO at all, it is just an attempt to encapsulate global variables which would be anyhow accessible using "extern"... Can't see a real advantage, and I thing the "factory" function that does: obj.a = a; etc. it is mixing creation with initialization... – vulkanino Feb 01 '12 at 08:32

4 Answers4

3

It is pretty hard to understand the gist of the question. It seems to me that your main interrogation is whether:

  • "simple" data + functions

is better than

  • objects

in some situations.

No. It's equivalent.

With the exception of exceptions (!) any code that you express in C++ can be expressed in C. It is just a matter of syntactic sugar that make the C++ counterpart easier to read. And before the naysayers jump on the bandwagon, yes virtual tables can be emulated in C.

Still, I would rather use C++. Compiler-checked encapsulation (private), compiler-driven overload selection, compiler-boilerplate (templates). It is just syntactic sugar, but such sweet sugar.

That being said:

class Foo {
  Foo() {}

  friend Foo build(int a, int b);
  friend int getA(Foo const& foo);
  friend int getB(Foo const& foo);

  int a;
  int b;
};

can be thought of as Object Oriented.

EDIT: simple and dummy example of polymorphism.

#include <stdio.h>

// Interface
typedef void (*FunctionPrint)(void const*);

typedef struct {
  FunctionPrint print;
} PrinterInterface;

void print(void const* item, PrinterInterface const* pi) {
  (*pi->print)(item);
}

// Foo
typedef struct { int a; } Foo;

void printFoo(void const* arg) {
  Foo const* foo = (Foo const*)arg;
  printf("Foo{%d}\n", foo->a);
}

PrinterInterface const FooPI = { &printFoo };

// Bar
typedef struct { char* a; } Bar;

void printBar(void const* arg) {
  Bar const* bar = (Bar const*)arg;
  printf("Bar{\"%s\"}\n", bar->a);
}

PrinterInterface const BarPI = { &printBar };

// Main
int main() {
  Foo foo = { 1 };
  Bar bar = { "Hello, World!" };

  print(&foo, &FooPI);
  print(&bar, &BarPI);
}

Result:

Foo{1}
Bar{"Hello, World!"}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I will naysay :) that you can't have polymorphism in C or any other procedural language in any way. – vulkanino Feb 01 '12 at 08:36
  • @vulkanino: what do you think pointer to functions are for ;) ? – Matthieu M. Feb 01 '12 at 08:39
  • @vulkanino: I edited my answer to include polymorphism, in C. – Matthieu M. Feb 01 '12 at 08:51
  • nope Matt - poor readability apart - when calling print(&foo, &FooPI) the caller has to KNOW (and create one ad-hoc) the second parameter type. this is not type independence. try to write a print function that takes a "printable" interface object and call it twice with heterogeneous parameters (foo or bar), which IS a printable type. what you can't have here is a type that behaves like another type even if it's different, that is - type independence. – vulkanino Feb 01 '12 at 09:20
  • @vulkanino: well, I disagree :) The compiler does not pass the pointer to the virtual table for you, so you have to pass it explicitly. But within the `print` function you have no idea about the "real" type that was passed. Of course you could sugar coat it by wrapping pointer to object and pointer to table into a single structure (ala Go) or by declaring that the first 8 bytes of the object passed in are always a pointer to table and can be casted (ala C++). The principle, however, remains identical. – Matthieu M. Feb 01 '12 at 09:37
0

As far as I know, such declaration exist just because some common headers (mainly coming form OS APIs: think to windows.h or "xlib.h") have to be used in both C and C++ programs, of unpredictable C and C++ versions.

If such API are rewritten as per today (Note: The API themselves, not just the interface), they will probably not have those kind declarations. Is a sort of "bastard coding" that makes the API developer sure about the memory mapping (important when a struct binds to hardware or external binary formats) and about "magic number definitions", that are never repeated in different headers for different languages.

Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
0

No I don't think there is any case in C++ where it would make sense.

The most obvious case where you would want to use the C approach is interrupts and thread callback functions. But those can be written as private static members in C++, which is to prefer.

Generally, there are very few cases where it makes sense to even return a pointer from a function in C or C++. Your example isn't good OO, you force the user of your class to use dynamic allocation when there is no need for it. You usually leave allocation to the caller, when writing OO code in C.

I can come up with a few cases in real-time embedded programming when you don't want any constructors to be called and therefore avoid classes entirely, but in such cases you would probably not use C++ at all.

Lundin
  • 195,001
  • 40
  • 254
  • 396
0

Just recently, I've begun to use Horde3D, that implements (more or less) what you describe. Internally implemented in C++, exposes a simple API in C.

This approach make it attractive for people that, like me, want to reuse the engine by means of a foreign language interface. I'm working on coupling the engine to SWI-Prolog, and since Prolog doesn't follow OOP orientation, there is nothing to gain in using (for instance) OGRE interface. OGRE has more features, but simplicity can have his merits, too...

CapelliC
  • 59,646
  • 5
  • 47
  • 90