Return Styles: Pseud0ch, Terminal, Valhalla, NES, Geocities, Blue Moon. Entire thread

C vs C++ vs Something Else

Name: Anonymous 2010-04-07 17:09

Hi folks,

After getting frustrated that I couldn't easily accomplish what I wanted to in C, such as growable type-safe arrays and inheritance/polymorphism, I recently partially converted my ~10ksloc game project to C++. I didn't convert it all at once; I just made it all compile as C++, and gradually converted it as I worked on new stuff. About half of it is OOP/templates, and the other half is still pure C99 which compiles as C++. Notice I said 'easily'; I did write a type-safe vector template as a macro which you explicitly instantiate, and I did have a reasonably complex inheritance tree by writing vtables manually. These were just a pain in the ass to maintain.

Now I am somewhat regretting this decision. I'm realizing that these things that pushed me to convert it to C++, I shouldn't really have been doing them in the first place. Growable vectors (such as std::vector) are a terrible idea. Dynamic memory allocation is just a bad idea in general. You can't use these because an allocation might fail at any moment. Whether you throw an exception or you correctly handle and propagate a memory error via return values, it doesn't matter; you can't usefully recover in the middle of an operation. The only sane way is to pre-allocate everything you need at load time. If you do this properly, growable vectors are useless.

And complex inheritance trees are just bad. I should instead have been doing pure polymorphism, without inheritance. There's still a bit of overhead in writing a vtable, but there's no complexity. I spent so much time figuring out what methods go in which object, and shuffling them around from base classes to subclasses and back again... I should have learned to separate my algorithms from the objects they apply to.

I'm also finding that all the supposed advantages of C++ are useless or inferior to C. C++ encapsulation and header file management is even worse than C, because I have to expose my structure and private methods causing endless rebuild cycles. Creating callbacks is WAY more complicated than it should be (have to define an interface and then implement it instead of just handing over a function and struct pointer.) What is the point of all this?

The only feature I see myself using right now that isn't completely useless is overloading on types. Namespacing all my functions manually is a pain, and the argument that it helps readability doesn't really fly.

Lately I've been contemplating creating my own toy language, which is basically C except it doesn't use header files and allows overloading on types. There are tons of quicky compiler tutorials out there using tools like Flex+Bison; I am sure I could create a C99 parser which additionally allows overloading on argument types, and simply outputs C99 with header files and fully namespaced functions (or just calls LLVM directly).

Ironically this sounds a lot like Go, except I find Go has made a whole lot of bad decisions: garbage collection is a horrible idea for a systems/game language, enforcing encapsulation / exporting functions via naming conventions (uppercase wtf??), whitespace is starting to matter (notice what they're doing with removing semicolons, and all the bullshit wierdo special cases, like where you have to leave an extra trailing comma if you break up your argument list in different lines).

Would a language like this interest anyone? Should I just keep my project the way it is, half C++ and half C? What to do?

Name: Anonymous 2010-04-07 19:52

>>7
How often does that happen to you? And how exactly does your game project is supposed "meaningfully recover" from the OOM?
In the middle of gameplay, it can't really. That's kindof the point, and why stuff like std::vector<> should not be used. During loading though, you can pop up an error that says something like "Ran out of memory loading level. Please close some open programs, or lower the texture detail level."

Ah, I see. Let me guess, your game has not yet gone beyond the stage of drawing a rectangle on the screen, maybe even a moving one.
As I said, it's about 10ksloc. It's fairly large and, yes, playable. It's got a minimal working 3D engine which does the tasks I need quite well (simple stuff like loading textures and meshes, geometry deformation and animation, etc.) I started using polymorphism to build a new rich animated UI (since as they say, OOP and GUIs kindof go hand in hand.) I didn't have a need for polymorphism until then. And yes it does solve a real problem; the resulting code I have now does actually work and makes a nice GUI. I just don't like it.

Recovering from OOM is not important, you are writing a goddamn game. Hand-optimizing vtables and doing other crazy shit is not important, because it is not a bottleneck.
WTF? I never said hand-optimizing, I said writing manually. You know, because you HAVE to, since C doesn't have syntactic support for polymorphism.

I don't really understand why you get the impression that I'm not a programmer (or why you're so hostile for that matter). I've worked in the game industry for several years now, and have a number of titles under my belt. This is just a side project, which is why I've been trying stuff like abandoning C++ (since I'm sick of using it in my day job.)

>>8
And thirdly, do you really need all this complexity for your game?
No, of course not. As I said it would basically be C but without all the manual namespacing. This is just a side-project, so I may as well have fun with it.

>>11
Yeah, this is basically what it was like in C before I converted it to C++. Except if there were more than a couple virtual methods, they'd go in a separate static struct, and a pointer to it would exist in the superclass. Like this:

typedef struct Creature Creature;

typedef struct Creature_vtable {
  void (*kill)(Creature*);
  void (*eat)(Creature*, Creature*);
} Creature_vtable;

struct Creature {
  Creature_vtable* vtable;
  int health;
  int hunger;
};

inline void CreatureKill(Creature* self) {
  self->vtable->kill(self);
}

inline void CreatureEat(Creature* self, Creature* other) {
  self->vtable->eat(self, other);
}

void CreatureKillImpl(Creature* self) {
  // base class implementation
}

void CreatureEatImpl(Creature* self, Creature* other) {
  // base class implementation
}

static Creature_vtable Creature_vtable_impl = {
  &CreatureKill,
  &CreatureEat
};

void CreatureInit(Creature* self) {
  self->vtable = &Creature_vtable_impl;
  self->health = 100;
  self->hunger = 0;
}


Then supply different implementations of these methods and a different vtable for each subclass, setting the appropriate vtable when you construct it. You get the idea. Basically the same code a C++ to C compiler would generate. Hence why I converted it to C++; code like this is annoying to write and maintain.

Name: Anonymous 2010-04-07 20:00

>>11
Inheritance starts to get difficult when you try and make expressions like bear.health -= 2; work properly. But that's just syntactic sugar because you know that a Bear is a Creature anyway.
Not really that difficult actually; you just access them through the superclass field. Just to complete >>15:

typedef struct Bear {
  Creature super;
  int poisonousness;
} Bear;

void BearKillImpl(Creature* cself) {
  Bear* self = (Bear*) cself;
  // implementation
}

void BearEatImpl(Creature* cself, Creature* other) {
  Bear* self = (Bear*) cself;
  // implementation
}

static Creature_vtable Bear_vtable_impl = {
  &BearKillImpl,
  &BearEatImpl,
};

void BearInit(Bear* self) {
  CreatureInit(&self->super);
  self->vtable = &Bear_vtable_impl;
  self->super.health = 200;
  self->poisonousness = 50;
}


Also the original vtable was wrong, leading to a wonderful infinite recursion, a good example of why code like this is error-prone:

static Creature_vtable Creature_vtable_impl = {
  &CreatureKillImpl,
  &CreatureEatImpl
};

Newer Posts
Don't change these.
Name: Email:
Entire Thread Thread List