|
A Postmortem of Game Programming with Digital Mars’ D Programming Language
Various Notes on Dwritefln
writef(“%d + %d is %d\n”, 1, 1, 1 + 1);However, if you are in a hurry and want a bunch of debug stuff dumped out: writef(filename, “ “, line_number, “\n”);Even better: since every printf I’ve ever written in C has had \n tacked to the end of it:
writefln(“%d + %d is %d”, 1, 1, 1+1); writef is type safe. It auto-detects sign, so no more %u / %d nonsense. You can also use %s for every insertion and D will lexical cast everything into a string before displaying.
StringsIn D, strings are a built-in part of the language. But it’s better than that. In D, arrays are a first-class part of the language. Strings (being arrays of characters) benefit from all the cool things you’ve ever wanted arrays to do. return array[4..length]This is possibly one of the coolest single lines of code I’ve ever seen. It’s free! Because I’m making no modifications, but am just returning a slice of an array, the line of code above uses 8ish bytes of memory and no processing time. Also note that length is automatically scoped inside array.
Unit TestingOn these first two projects, I didn’t use D’s built-in Unfortunately, the simplest version assert is about the only built-in testing function. More about that next. Code generationIn C, macros are seriously powerful tools. I don’t accept the arguments of people that macros are ugly, type-unsafe, code-unfriendly, and un-debuggable. That is all perfectly true, but in C/C++, there are jobs which can be done either only by macros, or can be done best by macros. Inline templated functions help, but macros still fill a niche.
D doesn’t have a macro preprocessor. There’s static if, static assert, templates, delegates and mixins. The big point here is that there isn’t a separate macro language you have to learn. The entire D language is available to you AT COMPILE TIME! (There are also nested functions, which help alleviate the macro problem, but that’s a different topic.)
Unfortunately, D ends up being short a little functionality by removing the full preprocessor. After working with the language a little, I wanted to add a more comprehensive
#define assert_equal(param1, param2) \
do { \
if((param1) != (param2)) { \
printf(“Test %s == %s failed.\n”, #param1 #param2); \
printf(“%s != %s\n”, as_string(param1), as_string(param2) ); \
assert(0); \
} else { \
printf(“Test %s == %s passed.\n”, #param1 #param2); \
} \
} while (0)
This has the problem of evaluating the params too many times, but gets close. The important things that I want here are that the text of the test (Test obj.name == foobar passed) and the reason for the failure (bazdoo != foobar) are both printed. Also, when this code is run in a debugger or the assert(0) line fires, I want the file / line number reported to be that of the client code, not the file location of the macro.
I have made several attempts at this and failed one of my three requirements. Using mixins (evaluate a string as code at compile time) is ugly. Instead of assert_equal(obj.name, “foobar”), I end up with mixing assert_equal(“obj.name”, “\”foobar\””). This code also doesn’t report the failure line correctly, and I couldn’t get a __LOCATION__ (defined as __FILE__, __LINE__) compile time variable to work. Using a normal function prevents me from reporting both the text of the test and the correct failure line. For example broken code, see the appendix.
DocumentingBuilt-in source documentation. I realize Java and C# and other development platforms offer this. D is nice in that this is part of the basic compiler (add one flag and you get a nice HTML file showing your documentation) and that it was designed in from the start. Further, the documentation thing isn’t some separate source file based xml parser: It’s part of the same language. (CONSISTENCY!) Add an extra trailing comment character to your comment: /// I’m a documentation comment /** so am I */ /++ and so am I! +/… and the compiler does the rest. Because this is built-in to the language and is so easy to use, you’ll use it too. There isn’t a separate mark-up language you have to learn (though there are some features if you care) it’s all part of D, and D is a clean language. Using CandyDoc as part of your project gives a nice sidebar and some syntax coloring. Automatic variable typingautos are really, really nice. In C++, if you want to iterate across a list, you have to do some nonsense like: for(listD recognizes that this is complete nonsense. The compiler already knows what type my_list is of.
for(auto iter = my_list.begin(); iter != mylist.end(); iter++) …And because D has a for-each: foreach(auto iter; my_list) …Call me crazy, but that looks like something C++ should have had all along. autos are also nice for weird functions where you just need the return value of a function. auto object_of_some_oddly_named_return_structure = unstd.odd.count_fibwotzes(); Problems with object pointersI had noticed earlier that D doesn’t have the -> or * operators. The language seemed to really downplay the differences between variables, references, pointers and smart pointers. I thought that it was a nice touch, since the compiler knows what each type of variable is and doesn’t need a syntactic hint. However, I got several problems trying to build my objects and later in building a vector class for my objects. D objects are all dynamically allocated – always. my_class my_object; // This is a pointer thingie initialized to null my_struct my_struct_obj; // this is a struct object – actual variable my_struct *my_pointer; // this is a pointer initialized to null my_class my_real_object = new my_class; // this is a class object ready to use my_object.do_stuff(); // crashes my_struct_object.do_stuff(); // fine my_pointer.do_stuff(); // crashes, but you knew that my_real_object.do_stuff(); // fineThe next thing that bit me was that since every class object MUST be dynamically allocated, there is no way to define that every object has an internal position vector (using class) defining its position. I had to change the vector type to struct and it was fine. I seem to remember Java works this way, and it’s an important difference I overlooked when starting. I also had trouble checking object pointers against null. I hated this one. Because the difference between pointers, references and object variables is mildly hidden, checking an object for null is different than in C.
if(my_obj == null) { // Crashes!
That line of code calls the virtual opEqual function defined for my_obj (which might be null) Trying to grab addresses off of my_obj is just wrong.
if(&my_obj == null) { // JUST PLAIN WRONG
This won’t crash, and will never return false - & is checking the address of the reference variable my_obj, not its contents.
Apparently, the right way is to use a special is operator for this.
if(my_obj is null) { // yeah!
I also got some mileage out of ! and direct eval but I like is better.
if(myobj) {
if(!my_obj) {
Object member functionsAs I build my classes, in this project particularly the vector class, I generally don’t add member functions or functionality until I’m actually using it in the client code. D’s operator overload is a bit different than C++’s because the operators are all named as functions (opMul, opAdd), instead of as operators (operator*, operator+). This adds clarity as to what the functionality being added is, instead of drawing attention to the specific syntactic sugar the language will let you use in calling. I think this naming scheme (while requiring a function name look-up to add operators) also reminds users that these functions can be used in callbacks. Nested functionsNested functions are cool. You can use them both as parameters for delegates and as static nested functions they are a nice way to handle simple topical tasks. In C++ I would have either done a huge block of logical statements or a separate static function placed earlier in the file. This moves the control logic away from where it is actually used. In D, you can define a little utility function (with or without access to all the variables of the parent function) and use it multiple times thereafter. One way to think of non-static nested functions is like class member functions, except instead of a hidden data pointer to an object of that class being passed to the function, there is a hidden data pointer to all the local variables of the parent function.
|