How to convert SMAUG to C++

Posted by Nick Gammon on Wed 21 Jan 2004 01:01 AM — 5 posts, 14,846 views.

Australia Forum Administrator #0
As part of another thread (programming with the Standard Template Library (STL) ) I wanted to show how you could use STL in SMAUG.

However the initial problem was that SMAUG is written in C, not C++.

I have converted it to C++, however it was a tedious process - to say the least.

One major problem was the use of the word "class" (being the player's class) in many places in the source, however "class" is a reserved word in C++. Also, to a lesser extent, the use of the word "new".

This meant finding and replacing each word "class", however not inside comments or quoted strings, to "mudclass".

There were also other fixups to do with stricter checking of the "const" keyword, and various other fiddly things.

To save anyone else the trouble of doing it, the chanages are in this file:


http://www.gammon.com.au/files/smaug/smaug1.4a_mxp.cpp_diffs.txt.gz


This is a gzipped file of differences (context diffs), between the C source and the C++ source, for the version:


http://www.gammon.com.au/files/smaug/smaug1.4a_mxp.tgz


In other words, the Unix source of version 1.4a *with* the MXP patches.

If you try to apply it to the non-MXP version it should work, except that some patch hunks will fail, particularly in comm.c.

To apply, download the patch file, unzip it, and copy it to your "src" directory. Then type:

patch < smaug1.4a_mxp.cpp_diffs.txt

You should then be able to "make clean" and then "make".

In order to keep the same file names for patching purposes the C++ source files still end in .c, however once you have patched them you may wish to rename them as .cpp files.




Why convert?

Once you have SMAUG in C++ you can use STL which makes handling lists and vectors much easier, and gives you the "string" class which lets you handle string data without having to "malloc" and "free" all the time, and worry about strings overflowing their defined lengths.

However the converted version presented here is simply exactly the same code with the minimal modifications made to make it compile as a C++ project.
Amended on Tue 01 Aug 2006 11:48 PM by Nick Gammon
Australia Forum Administrator #1
Just to clarify my earlier post - the patches given above convert SMAUG so it will *compile* under the C++ compiler (g++) however they are not a full conversion to C++.

To do that properly, you would need to convert "malloc" to "new" whenever allocating new things (like player data, object data, room data, and so on). The reason this is important is that "new" will call the constructor for any class contained within things.

For instance, if you amended the "char_data" structure to include an STL "string" type it would not be properly constructed by malloc.

eg.


struct  char_data
{
    CHAR_DATA *         next;
    CHAR_DATA *         prev;
    CHAR_DATA *         next_in_room;
    CHAR_DATA *         prev_in_room;
    CHAR_DATA *         master;
    CHAR_DATA *         leader;
    FIGHT_DATA *        fighting;
 ...

    string   some_string_data_here;
}


Here is an example from db.c:


    CREATE( mob, CHAR_DATA, 1 );
    clear_char( mob );



Instead of doing that you would do something like this instead:


  mob = new char_data;


And, put all the "clear_char" stuff into the constructor for that object.

Then, put the code for "free_char" into the destructor for it.

Also, of course, instead of using DISPOSE to get rid of a character you would "delete" it.
Australia Forum Administrator #2
I notice that some of the changes I suggested in the patch are in fact wrong. Strangely, they made it through the compile-and-test phase.

Specifically in a number of places (20 or so) I had an error when compiling this in C++;



   word   = feof( fp ) ? "End" : fread_word( fp );


The error was:


invalid conversion from `const char*' to `char*'


This was because "word" was defined as "char * word" and "End" is a const char *.

So, I changed it to:


   if (feof (fp))
     strcpy (word, "End");
   else
     word = fread_word( fp );


However while this compiles, it is wrong. The field "word" is declared like this:


    char *word;


The problem here is that my change does a strcpy to a pointer that is not initialised. So, the workaround is to do this:


  word = feof(fp) ? (char *) "End" : fread_word(fp);


This is not quite as neat, as it is casting a literal to a char, which is not very good, but that is better than copying into uninitialised memory.

I think the reason I got away with it, is that in fread_word it returns a pointer to a static buffer, a technique I am not wild about:


char *fread_word( FILE *fp )
{
    static char word[MAX_INPUT_LENGTH];

....

    return word;
}


So, what would happen is that, provided there was at least one entry in each file (eg. the class file) then the "word" field would be filled in to point to the static buffer "word" inside fread_word, and thus was allocated from then on.

It is really a mess, and shows how obscure bugs can creep in and hit you months after you have made them.

Amended on Thu 12 Feb 2004 02:50 AM by Nick Gammon
USA #3
Interesting to note on the char *word thing, if you change the declaration of that to:

const char *word;

it works just as well and doesn't require any further changes. I ran into the same when I undertook the pain of making AFKMud compile in g++.
Australia Forum Administrator #4
You are right, and that is a better solution, as it doesn't involve a cast.

I always get confused with "const", and in this case I assumed that because we were putting something *into* word, it couldn't be const.

However there is a difference between a const pointer and const 'what it points to'. In this case we can change the *pointer* 'word', but not what it points to.

For example, if we had wanted to do this:

word [0] = toupper ( word [0] );

... then that would not have worked.

I have written a little program to illustrate the differences, in case anyone is keen to know what I am talking about ...


int main (void)
{
char * a = "alpha";              // declare a as pointer to char
const char * b = "bravo";        // declare b as pointer to const char
char * const c = "charlie";      // declare c as const pointer to char
const char * const d = "delta";  // declare d as const pointer to const char

a = "Nick";    // change the pointer
a [0] = 'x';   // change what it points to

b = "Nick";    // change the pointer
b [0] = 'x';   // ERROR: assignment of read-only location

c = "Nick";    // ERROR: assignment of read-only variable `c'
c [0] = 'x';   // change what it points to

d = "Nick";    // ERROR: assignment of read-only variable `d'
d [0] = 'x';   // ERROR: assignment of read-only location
} // end of main



In practice, this program is not good programming, because the attempts to change the variable are actually changing a literal, but the important thing is to illustrate the compiler errors on the various declarations of const.

The four declarations (a, b, c, d) are the four different ways you can have "const" with a pointer.

It is interesting to use the program "cdecl" if you have it on your system.

For instance, you can "explain" a declaration, like this:


$ cdecl
Type `help' or `?' for help
cdecl> explain const char * const d
declare d as const pointer to const char


This explains what something does, or as the help puts it:


explain <gibberish>


It also works the other way, so you can type:


$ cdecl
Type `help' or `?' for help
cdecl> declare d as const pointer to const char
const char * const d