Jonathan's Blog

Monday, May 8, 2006

C++ Abuse

I'm reading through MUD Game Programming by Ron Penton. I found it at Fry's for $16.99, so I'm hoping it will be worth it at least for the discussions on network code (he only covers Telnet, as it's for MUDs, but adapting to other protocols isn't hard) and the fact that this book actually goes into management of the actual game, rather than just backend stuff.

Most of the books I've bought cover all kinds of info about how to write the various bits of a game engine, but they leave you with bits and not all that clear of an idea how they all get put together. Couple that with me being unable to ignore perfectionist tendencies and you'll have a good idea of why I take awhile to get very far in my personal projects. :) This book is cool because it focuses on something so simple: your average MUD is text based; the most complicated part is handling the bits that go over the network. That leaves Mr. Penton plenty of time to discuss how it all works together.

He has enough room that he writes two full MUD engines; the first very simple and similiar to older-style game. It's mostly hard-coded so any real changes require access to the codebase. His second engine is a great deal more flexible, and includes Python bindings and all that jazz.

So for $16.99, I'm stoked. It's a good book. However, I would ask anyone who might be interested in reading it to have a firm grasp of how C++ works and what you can, can't, should, or should not do in it, because Mr. Penton abuses the language in a number of ways.

First up, can anyone explain why you would do this:

class MyClass {
public:
int& MyVariable() { return m_myVariable; }
private:
int m_myVariable;
};

Rather than just:

class MyClass {
public:
int m_myVariable;
};

With the first variant, you have to call MyClass::MyVariable() to get access to the variable, but it returns a reference, so you can do things like:

MyClass m;
m.MyVariable() = 14;

What the hell is the point in having the variable private in the first place? It is no longer protected from getting modified in bad ways, and you're still exposing that implementation detail to the world. If you want a variable to be accessible to everyone, just make the darn thing public. If you don't want it to be, use get/set functions. It's much clearer what you're trying to do, and you have more flexibility down the line to evolve the interface further.

Along the lines of accessor functions, Jonathan Blow makes some very good points in his "Implicit vs Explicit Software Engineering Costs" rant here: http://number-none.com/blow/rants.html. I personally tend to use accessor functions when I want to either 1) validate things before I set them, or 2) allow read-only access to variables. If it doesn't fit in either category, it goes public.

Second nitpick on MUD Game Programming is C pre-processor abuse. Mr. Penton defines a couple macros that look like this:

#ifdef WIN32
WSADATA g_wsadata;
#define StartSocketLib WSAStartup( MAKEWORD(2, 2), &g_wsadata);
#define CloseSocketLib WSACleanup();
#else
#define StartSocketLib {}
#define CloseSocketLib {}
#endif

And then uses them in the code lookin' something like this:

void main() {
StartSocketLib;
// do networky stuff
CloseSocketLib;
}

I'm reading through the example code and see something like "StartSocketLib;" on it's own in a line of C++ and my brain halts. It's not immediately obvious what that statement does: it should do absolutely nothing. Any variable or function placed like that evaluates to a NOP. I understand completely hiding OS-dependant details behind some pre-processor magic, but if you're going to do that at least make the macro look like valid code.

Anyway, I bought the book for the ideas within it, and they're worth what I spent on it, but the code drives me nuts, so it's been slower going than I'd like it to be.

Wednesday, May 3, 2006

C++ Annoyances

Just one C++ annoyance actually. Having the ability to create an object on the stack complicates memory management a bit. I've got all my objects derived from this base class that provides reference counting for memory management. So I've got this decision now: should an initially created object have 0 or 1 references? I'm of the opinion that it should have one, because something obviously created it, so it's holding a reference to it.

For debugging and safety's sake, I throw an assert in the base class' destructor that will fire if the object is freed with any references left. Nice for debugging, but it doesn't work if I create a base-derived object on the stack. If I do, it'll be created with one reference and be automatically destroyed as soon as the function ends, firing off the assert. I can't just call release() on the object, 'cause release() will try and free the object because its ref-count will drop to 0, but it can't do that for an object created on the stack.

I like Objective-C and Java's approach here better: you can't create an object on the stack. You have to explicity allocate the thing, no magic.

I prefer Objective-C over C++ in general, but the lack of good tools outside of OS X have been keeping me from using it. I'm addicted to Visual Studio. :)

As an aside, I do understand that being able to create objects on the stack in C++ is what makes Resource Acquisition As Initialization (did I get that right?) work, but that's really just a workaround for not having garbage collection anyway. Not that Objective-C has garbage collection (natively), but the omnipresent reference counting and autorelease pools do a good job of hiding it.