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

Pages: 1-4041-

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 17:10

wow that was a lot longer than i wanted it to be.

tl;dr c++ sucks and the features it offers are useless

Name: Anonymous 2010-04-07 17:13

Convert it to D.

Name: Anonymous 2010-04-07 17:35

Convert it to Lisp.

Name: Anonymous 2010-04-07 17:41

Convert it to x86 assembler.

Name: Anonymous 2010-04-07 17:52

Convert it to Ada.

C and C++ are the most over used languages, people spend more time fighting the language than actually programming anything. If you want to get programming done instead of trying to hide/clean up C/C++'s bad habits, then try Ada

Name: Anonymous 2010-04-07 18:03

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.

How often does that happen to you? And how exactly does your game project is supposed "meaningfully recover" from the OOM?

I did have a reasonably complex inheritance tree by writing vtables manually.

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.

You are not a programmer yet, man. The necessary quality of a programmer is the ability to see what is important and what is not. Churning out clever code does not a programmer make, the code is worth anything only if it solves a real problem.

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.

I too went through that phase when I invented complex, generalized frameworks solving nonexistent problems. Yeah, endlessly shuffling methods around trying to make the API nice and easy to use without ever actually using it is one of the most distinct hallmarks of this phase.

The cure is: stop this bullshit immediately. Write something that works passably, call it a prototype and start writing the actual code solving the actual problems. Then you'll see what the real requirements for the underlying code are. Even experienced programmers can't predict this stuff in advance -- and what makes an experienced programmer is that he is aware of that and doesn't try too hard, preferring to leave the code as flexible as possible. Clarification: by "flexible" I mean simple, small, easy to throw away and rewrite from scratch, because any other clever way of achieving "flexibility" would fail. And you are not even a programmer yet, let alone experienced.

Go and write the damn game. Then look back and woe the time you wasted so uselessly before.

Would a language like this interest anyone?
No. You have to be a programmer before you design a programming language, no one could be interested in a music editing program written by a deaf person.

Name: Anonymous 2010-04-07 18:38

>>1
Lately I've been contemplating creating my own toy language, which is basically C
I have thought a little about this too, and I guess other /prog/-riders have as well. Things that I would add are namespaces, proper modules, simple inheritance (basically duplicating the fields of a struct) and perhaps built-in fixed-point integers (as in fix<22,10> using 22 bits for the integral part, and 10 bits for the fractional part).
But I'm not planning on actually doing something like this anytime soon - after all, I don't even know what C is really capable of as I haven't yet read a proper specification; second, language design (even if it's only supposed to be an extension) isn't something to be taken lightly -- look at what happened with C++. And thirdly, do you really need all this complexity for your game?

Name: Anonymous 2010-04-07 18:53

>>8
BCPL which C is based on is a fun toy language. It runs on a VM, is typeless and accesses all variables through pointers. After you learn about BCPL you can understand how C ended up so wacked out

Name: Anonymous 2010-04-07 19:03

>>9
I forgot to mention, you can get the current source for BCPL and Martin Richards homepage

Name: Anonymous 2010-04-07 19:26

>>8
simple inheritance
struct Creature;
struct Thing {
  int x, y;
};
struct Creature {
  Thing thing;
  int health;
  (void) (*kill) (Creature*);
};
struct Bear {
  Creature creature;
  int poisonousness;
};

It's simple (to implement).
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.

Name: Anonymous 2010-04-07 19:44

If OP actually creates a language worth using that can/will replace C, he will have my undying love. Shame that C will outlive us

Name: Anonymous 2010-04-07 19:46

What I thought of was something where:
class Actor
{
    int x, y;
};
class NPC : Actor
{
    int maxHP, currentHP;
};
would expand into:
struct Actor
{
    int x, y;
};
struct NPC
{
    int x, y;
    int maxHP, currentHP;
};

I believe that the fields would be laid out in memory in the order they were declared in, so that you could treat a NPC* as an Actor*, though keeping the alignment the same would need some additional work.

Name: Anonymous 2010-04-07 19:50

>>13-san doesn't seem to know that in Sepples structs are nothing but classes with public members by default.

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 19:56

>>14
You didn't understand my post. And by extension, I don't understand yours.

Name: Anonymous 2010-04-07 19:59

>>15
Well, in that case go on and have some fun. But be sure to put some thought into the design of your extension.

Name: Anonymous 2010-04-07 19:59

>>13
Memory alignment would not be a problem if `arrays' in your toy language were actually linked lists. Then you would be able to have arrays of multiple types, huzzah!

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
};

Name: OP 2010-04-07 20:04

>>13
You can't do it like this because converting NPC* to Actor* is a violation of strict aliasing. Actor has to be the first field in NPC, like this:

struct Actor
{
    int x, y;
};
struct NPC
{
    struct Actor actor;
    int maxHP, currentHP;
};


This is an exception to the strict aliasing rules, since an NPC* and Actor* can legally occupy the same memory address. CPython recently converted its implementation of PyObject to do inheritance this way so that it could compile with strict aliasing. You also don't need to worry about alignment this way (not that you needed to in the first place I don't think...)

Name: Anonymous 2010-04-07 20:16

>>19,15
I agree, classes emulation is a pain. But it's the only feature of Sepples I consider worth using (beside maybe macro overloading), so I don't feel it's really worth the change, especially as I do also rather dislike Sepples. Personal preference, though.

Name: Anonymous 2010-04-07 20:48

>>21
Worth the change to sepples you mean? Do you write vtables manually in C?

Also, what do you mean by macro overloading?

Name: Anonymous 2010-04-07 20:59

>>22
Yeah, and no, I don't usually make enough parent/child relationships for that, so it's usually just a case of putting any functions straight into the struct, as in >>11.

Macro overloading (or whatever it's called, it's 2 AM here*):
#define colour(r,g,b) (((r) << 16) | ((g) << 8) | (b))
#define colour(grey) colour(r,g,b)

i.e., the same macro name with different arguments do different things. I suppose that's a preprocessor thing though, so I wouldn't need fully-fledged C++ everything for just that. Hmm...

*I must sleep. Good night!

Name: Anonymous 2010-04-07 21:00

>>23
of course the second #define should be
#define colour(grey) colour(grey,grey,grey)

Name: Anonymous 2010-04-07 21:18

I really love how everyone falls back on garbage collection being a terrible thing in all situations.  It doesn't matter why the concept is invoked: it's just all situations.

Name: Anonymous 2010-04-07 21:24

>>23
Ah, I didn't realize C++ allowed that... Is there a use for it that wouldn't be solved with inline functions? I thought Stroustrup didn't want to add any additional macro functionality at all actually; why would they have added new features to it?

Name: Anonymous 2010-04-07 21:24

>>25
I assumed they were trolling, HYBT? HIBT? AIBMT?

Name: Anonymous 2010-04-07 21:32

>>27
You will never know.

Name: OP 2010-04-07 21:37

>>25
No, not all situations; I love that high-level scripting languages like Python have garbage collection.

Name: OP 2010-04-07 21:39

>>29
wasn't finished yet... I specifically said in >>1 that garbage collection is a bad idea for systems/game programming. You can't push the performance and memory limits of a system when you are using garbage collection.

But yes, in a high-level garbage collected language, you can get a whole lot done quicker when you don't have to think about object ownership (and when your application isn't performance/memory bound).

Name: Anonymous 2010-04-07 22:03

>>30
You can't push the performance and memory limits of a system when you are using garbage collection.
FYI: GC can be made to run fairly efficiently, enough that 'stutter' isn't a problem unless you're calling code every scanline. Okay, so maybe you are (which I doubt) but some GCs can be preempted, and it would be a good idea to do so in the hblank handler.

If you really want to push memory limits you can design your program so that GC is memory-efficient. Some do practice memory management in GC'd languages by allocating mutable data and dealing with it more or less manually. The interesting thing is that it's easy to build a framework for handling this memory (since you know what it is for, you can tailor your allocator) and I wouldn't be at all surprised to find examples of this that are more space efficient than malloc, and perhaps even faster.

I'll say this for sure: a good GC beats a naive malloc.

Postscript: I am not a big fan of GC, but I it seems people like to hate on it out of ignorance.

Name: Anonymous 2010-04-07 22:08

the problem with GC in compiled languages is that its machine dependent, GC is very easy to implement in VM languages like Java and C#

Name: Anonymous 2010-04-07 22:13

>>32
the blub with blub in blub blubs is that its blub dependent, blub is very easy to flub in blubble blobble like hefty smurf and smurfette
Fascinating.

Name: Anonymous 2010-04-07 22:56

>>33
Haha, I think you should go fuck yourself.

Name: Anonymous 2010-04-07 23:18

>>34
D'aww. But I made it just for you.

Name: Anonymous 2010-04-08 2:12

>>34
Haha, I think you should go fuck yourself.

Name: Anonymous 2010-04-08 7:28

>>20
the ISO standard for the C programming language (including its newer C99 edition) specifies that it is illegal (with some exceptions) for pointers of different types to reference the same memory location.
source: Wikipedia
Didn't know that, thanks.

Though later on, the article says:
This rule, known as "strict aliasing", allows impressive increases in performance
Could anyone elaborate on that, or is it just a lie?

Name: Anonymous 2010-04-08 7:50

>>37
I don't see how, unless there's some kind of type caching involved.

Name: Anonymous 2010-04-08 7:57

[b][sub]f[sub]y[sub]u[sub]o[sub]c[sub]u[sub]k[sub] [sub] [sub]s[sub]y[sub]h[sub]o[sub]o[sub]u[sub]u[sub]r[sub]l[sub]s[sub]d[sub]e[sub] [sub]l[sub]g[sub]f[sub]o[/b]

Name: Anonymous 2010-04-08 7:59

>>38
There is. Strict aliasing allows the compiler to decide that if two pointers have different types, they don't point to the same object, so if one is updated and the other is already loaded into some register, it doesn't have to reload the value in the register.
I don't know how much speed-up that actually gives you, though I can imagine it can be quite a bit on architectures with a lot of registers.

Name: Anonymous 2010-04-08 9:08

Name: Anonymous 2010-04-08 10:35

>>40-41
Thanks.

Name: Anonymous 2010-04-08 12:25

>>40
This is correct, but in addition, the compiler can also re-order computations performed on different types.

I don't actually like the strict aliasing rule though, mainly because it is obsoleted by restrict, and because there are lots of times I'd like to break it. For instance it fucks with most object systems (for this reason you can't compile Objective-C at all with strict aliasing). It also has lots of holes; for instance allowing anything to alias with char* just because it's common.

I'd much rather turn it off and use restrict explicitly in those few inner loops that have actually been shown to give a measurable performance boost. Explicit is better than implicit; it's always better to assume nothing and just execute the code as written.

Name: Anonymous 2010-04-08 12:27

>>43
*that should read 'to char*', not 'with char*', since you can't cast it back again.

Name: Anonymous 2010-04-08 19:31

>>43
C really likes to reserve the rights to call the shots for optimizations. You can make suggestions in the code about how to optimize, but you really only get to turn it down (with a few rare exceptions) on the command line. I agree that explicit optimization is really nice, but I see where they're coming from--it's a can of worms best left untasted.

Name: Anonymous 2010-04-08 19:33

>>45
CL has explicit optimizations/declarations to a certain extent, but not all assumptions you make are portable. Overall, it's a good thing.

Name: Anonymous 2010-04-08 19:55

>>45
Good job 45-san, searching for "a can of worms best left untasted" on Google will soon yield this page.

Name: Anonymous 2010-04-08 20:06

>>46
The can of worms I referred to is restricted to C. C rarely aims to compile to the sequence you wrote, but one that will have the same effect in the end. If you want to keep restrictions explicit, the same argument can apply to just about everything else, and suddenly you need to adorn every declaration with indications of how it should be optimized.

In each and every case I can see the benefits, but when you add them all up you have a problem. If that is what C was today, someone would invent a new language that took care of it implicitly. And it would be a hot topic. So I'm in favor of having a typical (default) optimization profile that can be tweaked from the command line, and fine-grained in the language. Really, C isn't far off from that (if you go off spec), but it doesn't seem to be complete in any implementation.

but not all assumptions you make are portable
Is that 'you' as in >>45? - or 'you' as in 'the programmer'? Which assumptions are these? The ones I make are that I will have to retain and review the assembly output if I want to know what is really going on. I'm paranoid enough in this that I maintain the assumption across compiler invocations of the same version, on the same system.

Name: Anonymous 2010-04-08 22:09

>>47
How soon? It's been like 2 hours already and still no result.

Name: Anonymous 2010-04-08 22:23

>>49
Blizzard's ``soon''.

Name: Anonymous 2010-04-08 23:22

>>50
wwwww

Name: Anonymous 2010-04-09 0:15

>>50
We used to call that "Real Soon Now" (RSN).

Name: Anonymous 2010-04-09 0:19

>>50-52
You children and your video games.

Name: Anonymous 2010-04-09 0:23

>>53
What! Listen here, jerkface!

Name: Anonymous 2010-04-09 0:29

>>53
``RSN'' predates Blizzard, dude. Hey, at least I don't play with dolls anymore.

Name: Anonymous 2010-04-09 6:11


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