Tracking down bugs is what programming is all about :) you will gradually learn techniques as you get more experienced. Some starting points:
Put asserts in you code. Asserts are really useful because they can show up bugs before the bugs become a big problem. Basically an assert is you saying 'I assert that something is true at this point in my program'. So for example if you have a function that works on a passed pointer and you are sure the pointer must not be null but must be valid you assert this fact at the top of your function. If the pointer ever is null you will trap it before it goes on to cause havoc!
E.g.
#include <cassert>
void SetName(char *name)
{
assert(name!=NULL);
strcpy(m_name,name);
}
Other times you may want to use asserts is to check array bounds (another common cause of bugs) so if you had a function to return an object by its array index you would want to assert that the index is in the correct range,
E.g.
CObject *GetObjectByIndex(int index)
{
assert(index>=0 && index < m_numEntries);
return m_objects[index];
}
Another common way of tracking bugs is to output values to the output pane in Viz. You can use OutputDebugString to do this (see the next question).
If an assert fails or your program crashes with some other error you can use the Visual Studio debugger to help find problems. This only works if you have run the program in the debugger (press F5) and not used the execute button (the exclamation mark - runs outside of the debugger).
When the error occurs you will have the option to press retry. Pressing this drops you into the debugger. Depending on your set-up you will have a number of different debug windows. To change which can be seen go to the View menu and select from the debug windows list.
The Call Stack window shows you the last few calls that were made prior to the crash. You can click on previous ones to go back through the code. At each point you can hover the mouse over variables to see what values they contain or use the watch window to type variables into. This way you can check to see what values variables have and deduce where the problem is. It takes a bit of detective work.
If you still find you have problems and your code activity is high you may think of logging what is happening either to a file or to the debug pane in Viz (or both). To output to the debug pane you write:
OutputDebugString("This is some output to the debug pane\n");
#pragma provides a way of setting options for the compiler. This is often compiler specific. In this case we are using 'once' which means we are telling the compiler that opening this file once has the same effect as opening it multiple times. Note that this is a hint to the compiler and not a guard condition. This helps speed up compilation when you include the same header file in many source files. Other #pragma uses are to turn off a warning message from the compiler (normally you do not want to do this as every warning is a potential problem). To turn off a warning message you write #pragma warning(disable: #) where # is the warning number.
Note: you often see headers use both #pragma once and the #ifndef #define (or !defined) guard method of forcing a header to be compiled only once. This is the correct way of using the pragma because it is a hint to the compiler and not a guard condition itself. Having said that you can just use #pragma once without a guard condition and often get the same effect but be aware of what you are doing.
#define is used to define values and macros that are replaced in code at compile time. It is important you remember this is done at compile time and not at run time. There are a number of commands you can use:
#define - defines a symbol to be a value or result of a calculation
#ifdef - if a symbol is defined carry out the following, you can also use: #if defined
#ifndef - if a symbol is not defined.
#endif - end of the if section
#else - just like a normal else
#elif - combines else and if
An example use of #define might be:
#define GRAVITY 0.98f
Anywhere in your code you can now use GRAVITY instead of writing 0.98f, this allows you to adjust the value in just one place at a later date. However this is no longer thought to be the best thing to do, the problem with defines is they have no type. For the above example you would be better defining a constant global instead, which allows you to specify type:
const float kGravity=0.98f;
So when can you use #define? Well it is useful for turning code on and off e.g. you may want to measure frame rate and put lots of timer checks in your code - but you don't want this on all the time. So instead of having to comment all the code out you use a define. e.g.
At the top of your file you may have:
#define ENABLE_FRAME_RATE
#ifdef ENABLE_FRAME_RATE
float gLastTime=timeGetTime();
int gFrameCount=0
#endif
Then later in your code where you want to measure the frame rate (after your render loop) you might have:
#ifdef ENABLE_FRAME_RATE
float timeNow=timeGetTime();
float timeOfFrame=timeNow - gLastTime;
.. etc.
#endif
Now to disable the frame rate calculations we just comment out the #define
Another example of #define is to define a macro. A classic example is one to find the minimum of two values e.g.
#define MIN(a,b) (( (a) < (b) ) ? (a) : (b))
The ? means if the condition is true return the first variable else return the second (after the colon). So the above reads - if a is less than b return a else return b. All the brackets are essential as you may be using this define in a complex expression. In fact this is one of the problems with defines, its very easy to forget the brackets and get the wrong results (often in just a few unusual cases). This define does take advantage of the fact that any type can be passed in so can be used on any type that defines the < operator. Some example uses might be:
float smallestHeight=min(heightA,heightB);
smallestHeight=min(smallestHeight,heightC);
As an example of the bracket problem imagine for some weird reason you want to define a macro to add 10 to a value, you might wrongly define it as
#define ADD_TEN(a) a+10
Then you may use it in the following cases:
int a=6;
int b=ADD_TEN(a)
In this case you would get the right answer, if you expand the define (as the compiler does at compile time) the expression would read:
int b=a+10;
Now as an example of where it goes wrong, say you used it in a calculation:
int a=2;
int b=8;
int c=ADD_TEN(a)*b
You might expect c to now equal 12 * 8 = 96, but in fact it equals 82. The reason is clear once you replace the macro to see what is happening, the expression becomes:
int c=a+10*8'
Since multiplication has a higher precedent than addition the 10 is multiplied by the 8 and then added to a giving you 82. So the solution is simply to put brackets in.
#define ADD_TEN(a) (a+10)
Then the expression is:
int c=(a+10)*8;
This is a very simple and contrived example of the problem, just be careful using defines that you don't fall into this kind of trap.
When strict is defined certain type casts and parameter passing in libraries are enforced more strictly. It is a good idea to have this defined to help track down bugs. Since it is a define it may be defined in a number of places so it is usual to do this:
#ifndef STRICT
#define STRICT
#endif
CTRL-F2 - places a bookmark on the current line (CTRL-K, CTRL-K in .net)
F2 - jumps to the next bookmark (CTRL-K, CTRL-N) in .net)
F5 - compile and run
F9 - places a breakpoint at the current line
F10 - steps through code, does not step into function calls
F11 - steps through code and does step into function calls
F1 - help for the word following the current cursor position.
The const keyword specifies that a variable or object is not modifiable. This is very useful to enforce rules about objects and often helps trap problems at compile time (it can also aid the compiler in optimizing code). You should use const from the start of your application as switching later is more difficult. An example of the use and benefits of const follows:
Say we have a large structure that we need to pass to a function for it to carry out some calculations. Perhaps it contains some values relating to a monsters stats, e.g. health, hit points etc. and the function needs to work out if the monster is healthy.
{
TMonster monster;
if (IsMonsterHealthy(monster))
{...}
}
bool IsMonsterHealthy(TMonster monster)
{
if (monster.health<10 && monster.hitPoints==0)
return false;
return true;
}
Note: this is an example only, if I were to implement the above in reality I would make monster a class and make IsHealthy a member function.
We are passing the monster structure to the function for it to carry out some calculations involving its member variables. The problem with the above is that we are passing the whole structure so a copy of the structure will be used in the function. This is not good as it creates slow code (a lot of data is copied). Also, if we pass an object, a copy is made and a new object created for the lifetime of the function only (if it were a class instance a constructor would be called and destructor on exit). So instead we might just pass a pointer to the function which is very quick as it is normally just 4 bytes:
if (IsMonsterHealthy(&monster))
...
bool IsMonsterHealthy(TMonster *monster)
{
assert(monster);
... etc.
}
That looks much better, however in this case because we are passing a pointer to our structure and not a copy the function could alter the member variables of monster. We may say that we will ensure it does not but there is always a risk especially when working in large teams of programmers, a programmer may be tempted to modify the data or just accidentally modify it due to a bug. E.g. if they forgot to put two = signs in the hitPoints comparison it would assign 0 to hitPoints instead of check if it is zero. Much better is for us to enforce the fact that the function will not modify the structure variables.
So we could instead pass in a const reference or pointer to the structure which enforces that the values in it cannot be changed. The use of a reference or pointer means a copy is not made and the const means any attempt to assign values to the structures member variables produces an error at compile time. So we could change the function to:
bool IsMonsterHealthy(const TMonster &monster)
{
// any attempt to modify the variables will produce an error e.g. monster.hitPoints=0 will not compile
....
}
Note: it is perfectly valid to use a const pointer rather than a const reference however I prefer the reference because it cannot be NULL where as a pointer can be. Generally if you want to pass a parameter that is an object that must exist and you do not want it modified use const T& however if the object may not exist use a pointer (const T*). The choice depends on the circumstance.
You should get into the habit of using const in function definitions, after a while it becomes second nature. It also forces you to think about what you are doing and what the scope of a variable is. A lot of bug solving is achieved by enforcing the rules you know should exist. It is similar to using asserts, with asserts you are saying 'This must be true' with constness you are saying 'this function will not modify this parameter'.
I have created a small word document that details most of the uses of const, you can access it here: Const Correctness.doc
You cannot. You could raise an exception but you cannot return a value. I normally just initialise member variables to defaults in constructors and nothing else. Any memory allocation or other initialisation I tend to place in an Initialise() member function. This function can also then return a success Boolean. However this does cause its own issues like how do you stop someone calling Initialise more than once? How do you make sure they call initialise at all? However if we look back at the idea of using exceptions we have another problem to do with memory allocation. If we allocate memory for a number of objects in a constructor and an exception is raised you may be left with undeleted memory because the object is only half created. There are solutions to this like using smart pointers and doing as much in initialisation lists as you can but it can then become complex. Probably the big question is do you want to use exceptions in your game code? Exceptions are a very good method of solving error situations but unfortunately there is a cost to using them. They make code larger and slower which is not what we want for our game so a lot of game programmers turn off exception handling completely (via settings in Viz). However if they improve the robustness of your code they may save a lot of development time and hence be worthwhile.
There is a useful flag you can set to cause memory leaks to be displayed in the output pane of the visual C++ IDE. At the beginning of your program you need this:
#if defined(DEBUG) | defined(_DEBUG)
#define CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif
At the entry point of your application e.g. in WinMain you will need to add this:
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif
In order to put break points on memory allocation numbers see this Microsoft link: How to: Set Breakpoints on a Memory Allocation Number
When writing in C++ you should prefer new and delete. What you must definitely not do is mix the two. So if you use new you must use delete and not free, likewise if you use malloc you must not use delete. Another gotcha is to forget to use the array form of delete. E.g. say you wanted to reserve memory for 10 integers:
int *intArray=new int[10];
....
delete []intArray;
You must use [] when deleting memory created using the array form of new.
Another side issue is that it is normal to check a pointer is not null before freeing the memory. The C++ spec. does however allow delete to be used on a NULL pointer - its not illegal. However you will often see code like this that checks if it is null, if it is not it deletes the memory and sets the pointer to NULL:
if (intArray)
{
delete []intArray;
intArray=NULL;
}
Note: you will also often see macros in DirectX sample code that do the above for you e.g. SAFE_DELETE
The one thing that C style memory allocation provides that is not so easily provided by the C++ form is to be able to alter the size of allocated memory using realloc. However if you find this is what you want to do an alternative storage form may be a better choice, e.g. use one of the standard library interfaces like list or vector.
Well there is certainly a body of thought that says arrays are evil and you should never use them. The reason is that going beyond the bounds of an array is a very common cause of bugs. I now avoid arrays wherever possible and instead use the standard library vectors or lists. Vectors have the advantage of being able to grow and still allow array-like access (random) to data. They are much more robust however and can control access to the data much better than an array. The argument against vectors is that they are slow, this is an argument often stated by games programmers, however I wonder if these programmers have ever really tested the relative speeds? I have done some tests myself and found vectors to be very fast (its actually a requirement of the C++ specification that they perform to a set speed order). Vectors are slow if you are removing elements a lot. I have found the std lists to be not quite as fast as my own home-grown ones. At the end of the day, with game code now so large, you should aim to use the safest method that leads to less potential bugs. Optimisation techniques can be applied later when the code is feature complete.
As a final point if you want to access data sequentially using a vector use an iterator for best speed, when accessing data randomly use the array form. If using an iterator to change values use container_type::iterator but if just reading values use container_type::const_iterator. You may also want to use reserve(...) to reserve memory in a vector before use. Also see the next item.
Note: you are not limited to just the standard library there are also other libraries available. The most popular one is the boost library that is free and is very well supported and used extensively.
If you are not going to be inserting or deleting elements very much use a vector otherwise use a list. Vectors are optimised for fast random access while lists are optimised for fast insertion and deletion.
It can become quite painful having to write std:: before everything. The reason it is there is to tell the compiler to look in the standard namespace for the functions. There is a way around it though via the using keyword. So instead of writing:
std::vector <int> intVector;
at the top of our source file we write :
using std::vector
This says that we intend to use the name vector to mean std::vector exclusively, and we do not intend to define anything named vector ourselves. We are importing an individual symbol from the standard library into our code scope. So we can now write:
vector <int> intVector;
We could also allow our code to use the whole of the standard namespace using:
using namespace std;
This means we are importing every symbol from the std namespace into our code scope. I think you should not use this method but instead import each symbol as it is needed, this makes it clearer what you are intending to another reading your code and keeps the benefits of namespaces.
The left hand side of an assignment must be a non-temporary object. You have probably tried to use a temporary object in an assignment.
Both increase the value of x by one. The first is a post increment operator and the second a pre-increment one. It is important to realise the differences as, when applied to objects, using the post increment operator can cause a temporary object to be created.
x++ This increments x, returns the original value of x so requires a temporary object.
++x This increments x, returns the incremented value of x so no temporary object needed (potentially faster)
In general you should use ++x
It was so easy in the days of C character strings to write into a buffer e.g.
char buf[2048];
sprintf(buf, "The value of integer a is %d and float f is %f \n",a,f);
There are problems with this though. It is prone to buffer overruns as sprintf does not take the size of the buffer and so relies on the buffer being big enough. Another problem is with character sets where you need to use alternative forms for UNICODE etc. Also the format specifies can be confusing. So with C++ it is logical to use the STL string for all text operations. The standard string has many functions but does not have a way of doing the above. Instead you need to use a string stream e.g.
std::ostringstream buf ;
buf << "The value of integer a is " << a << "and float f is "<< f << endl ;
std::string str = buf.str() ;
Please see the fuller notes on using C++ strings here: String Handling
A reference must be assigned to something at creation (unlike a pointer a reference cannot be NULL) so it may seem impossible to use one as a member variable. You can do it though if you initialize it in the constructor using an initialization list e.g.
class MyClass
{
private:
TSomething &m_var;
public:
MyClass(TSomething &var_, ...);
};
MyClass::MyClass(TSomething &var, ...):
m_var(var) // constructs m_var from var
{
//
}
I use two macros to do this. The process of creating them is a bit long-winded so I have created a page describing the process, it can be found here: Macros In Viz (Viz 6.0 only). For .net macros take a look on the CodeProject site they have plenty available.
Normally you can just type the name of a variable into the watch window where you can then examine its value, however with vectors this does not work. What you have to do is use the _first member variable of a vector, so to watch the ith element of a vector called entityVector you would enter the following to watch:
entityVector._Myfirst+i
You could also see all the elements (if you know how many there are) by using ,x in the watch window. This indicates there are x elements so to examine the first 10 elements in the vector you could do this:
entityVector._Myfirst,10
Note: under Viz 6 you have to use entityVector._first instead of _Myfirst
There is a converter available here: prjconverter
The academic version has the same functionality as the full professional version. The only difference is that you are not allowed to sell any software you write with it. Now there is a way around this, all you have to do is download the Visual Studio 2003 professional compiler from microsoft.com (for free) and compile your code with it. This frees you from the restraints of the academic version.
You can load Visual C++ 6.0 project (dsp) and workspace (dsw) files into .net just be loading them. To go the other way (from dsw and dsp to .net solution (sln) you can use this useful tool: http://www.codeproject.com/tools/prjconverter.asp
By correctly using precompiled headers. I stress correctly because I had been using them incorrectly for a long time until I discovered how to use them properly. The MSDN help on using precompiled headers is not very clear. Once you get them working properly compilation speed is improved enormously. I was surprised at how much of a speed gain they give. The way to get them to work is to set your project to use them through a header and select one source file as the creator of the precompiled data and all other files to use it. Below I describe the steps required to use precompiled headers in Visual Studio .net
If you get any compile errors after this make sure all the source files in your project have the same settings e.g. if one is set to use run time type checking and the others are not you may get errors.
New: Microsoft have released a patch for Intellisense that improves matters a bit. See the blog entry and follow the link here: Performance Improvements in Visual C++
Intellisense is the name given to the feature in Visual Studio that provides drop down lists of functions etc. It is a bit flakey to say the least! The first thing to do if it is not working for you is to make sure there is not an error in your code prior to the point you are trying to use Intellisense. Next you should do a build and then a rebuild all if that does not work. Even after doing this it still often does not work correctly. You can purchase other systems like the highly recommended Visual Assist.