--------------------------------------------------------------------------------------- WELCOME There are a lot of good and bad documents on how to code, this is another. This is specifically targeted at the game development community, which suffers from two serious evils in coding : the belief that you're in such a hurry to meet deadlines that you don't have time for clean coding practices, and the belief that every bit of code needs to be so optimized that you can't afford clean coding practices. Both of these are wrong, and I'll try to convince you of that. In fact, these two mistakes make game developers some of the worst coders in the industry. There are hardly any programming disciplines (databases, operating systems, applications, web development, etc.) where people swear by the false and old tenent that C++ is too costly, so they'll stick to C, but I've heard it many times from gamers. There's an evil irony in that the people who would take the time to read a document like this are probably already the "good" ones, because it means they're actively trying to improve their work. The "bad" ones will think themselves above this sort of thing, or just not be interested. That's a big mistake. Programming is all about efficiency, so any new trick or technique you can find to improve your efficiency or that of the team is a huge bonus. You should be reading books on programming, like Scott Meyer's "Effective C++", they really do have new and valuable things to say. As a manager of programmers, or just a programmer on a team, you must think : what is the job of a programmer? It's not just to write code that "gets the job done" - it's to write code which will result in the entire project being finished well and on time. That means that programmers need to write code which is efficient, easy to debug, easy to modify and extend, and easy for other coders to understand. These later parts are just as important as efficiency, and are often overlooked or not enforced. In order to acheive this, you must think of the code itself as part of the product. You cannot just think of what's appearing on the screen, because the code *is* the product at every intermediate step of the way towards completion. If you try to just "hack" something in which gets the right result, you'll end up being burned by other changes along the way. You must also not think of the code as your private little secret or property. You must think of the code as a public document, like a paper you're writing, which all your peers will be reading - since they will! They must be able to understand it, modify it, etc. --------------------------------------------------------------------------------------- EFFICIENCY IN THE RIGHT WAY When I arrived at Eclipse, I was fond of using plain C and doing nasty things like if ( *(++ptr) == (i = j) ) { ... because I had found in my early days that these kind of obfuscated expressions would turn into more efficient compiled code (I also took a perverse pleasure in it). Dave Stafford wisely told me to stop. He pointed out a key tenet of optimization which I was aware of but had't fully assimilated : if the code is really that important to speed, then you should write it in assembly language or something similar; if it's not, then it should be written for clarity, not efficiency. Steve McConnel (Code Complete/Rapid Development) has a good rule here : if you think that your code is "clever" or "tricky", then in reality it's just bad. If you think your code is a bit hard to figure out, then others will find it completely opaque. Whenever you do something "tricky" with the compiler or the language or some construct, you must document it and you better have a good reason for it. This is a form of the old Knuth 80-20 rule; 20% of the code takes 80% of the execution time (hence, optimize it!), while 80% of the code should just be written for clarity and ease of maintenance. Part of the problem in game programming is that most of us started out on Apple 2's or Amigas, or 286 PC's, where the CPU was so slow that you really did have to worry about optimizing all parts of the code. Hence we've got bad habits. Another problem is that C and C++ optimizers used to be very bad. They're not anymore. In fact, I challenge you to write assembly using plain integer instructions which is faster than optimized C code. It's possible, but it's very hard (optimizers still aren't so great with floating point, and of course if you use multimedia extensions you can win). Modern optimizers are so great, that code written for clarity can often end up faster than code written for speed. That's because it gives the optimizer a better chance to figure out what's going on and do the right thing. I'll go through a lot of specific cases of this later; in all cases, I'll be using the C syntax to make it more the operation of the code more blatant and restrictive and precise, and it will result in better optimization. Now there's the point of algorithmic efficiency versus optimizing code. The former is *MANY* orders of magnitude more important. If you have a brute force string matcher written in assembly, I can beat it using a clean C++ implementation of a suffix-trie searcher. Of course, algorithms and spot optimization together will always win, but that really takes too much time. Throughout the development process you need to be able to change your algorithms quickly, and too much early optimization can lock you down in a bad technique. I've spent a lot of wasted time optimizing, because no matter how tight you make a loop to draw a font onto the backbuffer, it'll still be too slow and you'll have to just render the font using sprites and textures - a much better algorithm. Even as recent as a few months ago, we spent a bunch of time here at Surreal spot-optimizing our landscape texture generator, only to find it was still to slow, so we threw it out and came up with a new algorithm. In this topic is the matter of C++ versus C. Many people are afraid of C++, and they shouldn't be. Instead, they should simply learn about it, what's going on, and how to make sure that it's efficient. Using C++ can greatly improve the clarity, cleanliness, maintainability, abstraction and modularization of code. So, the advantages are obvious. Where can C++ cause inefficiency : 1. virtual functions. Yes, they are a slight overhead. However, you should probably not be calling virtual functions in any of your tight loops. For example, your Vector class should not use virtual functions. Virtual functions are useful in class heirarchies where you're abstracting the child relationship, and this should only be done on your top level classes, which are used in the 80% of the code that don't need optimization. Hence, used correctly virtual functions are no problem. 2. exception handling. This is another overhead, but it's quite easy to get rid of : just disable it. You probably weren't using it anyway. If you were, it was probably being used as a crutch to avoid doing proper error handling. The problem with exception handling is that it requires the compiler to provide destructor unrolling for anywhere in the code, which adds overhead even to functions which have nothing to do with exceptions. 3. implicit class construction. This is a nasty one, and I'll talk about it more. Basically, this happens when a class conversion is performed by C++, or when you return a class by value from a function. This can be a nasty little inefficiency, but if you do things right, you can avoid it. I'll talk more about it in my list of specific tips, but there are a few basic keys : A. use the "explicit" keyword on constructors, and B. don't define any functions that return a class by value (this includes things like "operator +"). Some people love to make clever use of implicit class constructions with proxy classes and whatnot. Here, I defer to the "clever" rule - it's just bad code. I'm also very scared of anything that I can't plainly see in the code because I've spent so much time optimizing other people's code and I need to be able to figure out what's going on as easily as possible. There are some more evils to C++, generally caused by people who are enamored of the features but not aware of the costs, or forgetting what the real point is : clarity and encapsulation. These evils are things like: 1. over-use of pass-by-address. This can cause clarity problems because it's not obvious to the caller that the value he's passing in is being changed. For example, people like to grab a pointer, then pass *pointer (value at pointer) into a function which takes an address. That function is suddenly in the nasty position of having to validate an address, since it might be pointing at null!! 2. over-use of proxy types and templates, derived classes and operator overloads. In general, all of these things should only be used where really necessary and/or natural. For example, an operator ++ that draws a polygon is not wise. You should accomplish your goal with the simplest possible machinery. Good class design can actually provide the biggest improvement to efficiency possible these days : better memory access patterns. On all the modern game development platforms, cache missses are really the most expensive thing you can do (CPU's are very fast at math these days). Good encapsulation of classes lets you replace the data members with memory-use optimized forms that may be quite nasty (such as run-time compressed data) but all opaqued and hidden away in the class implementation. Thus an accessor may have no idea that the integer he just requested actually was stored in only two bits. The final rule of efficiency is to test it, and to examine the assembler. The latter is something that people don't do enough. Say you write some C++ and you're pretty sure that your operators and proxy classes are getting optimized out - well, don't be "pretty sure", tell the compiler to output the assembly and have a look, see what's really happening. You should never write obfuscated code for efficiency purposes unless you have hard proof that it makes a big difference. --------------------------------------------------------------------------------------- GOOD CODING PRACTICE, WHY IT'S WORTH IT When you start working on someone else's code, perhaps fixing a bug or adding a feature, I'm sure you wish that it was well commented, with clear variable names, and small function bodies. Bad code results in near-constant debugging, due to programmers' inability to understand how functions are supposed to be used, or unexpected side-effects of changing some un-protected variables. Not only does this slow down development, it makes programmers miserable, and miserable programmers don't write good code. One of the worst things about bad code is that it spreads. You might hope that it could be contained, and new coders could write better modules into the engine, but this rarely happens. Instead, all new code which refers to the bad old modules inheret the accesses to public variables and unclear function names and duplicated code. Furthermore, good coders working on bad code get frustrated and don't want to spend any excess time in that portion of the code base. The result is that they do shoddy quick jobs; they also are usually loathe to fix bugs or add enhancements to the bad portions. Many programmers are under the false impression that the most important thing they can do is to hammer out code lines. This is grossly false. Estimates are that for most programmers, less than 10% of your time is spent writing new code. Let's do some rough math. In a good month, I write about 2000 lines of code. Now, I can type a line in about 5 seconds, with some thinking (that's about the speed I'm writing this). So, just writing those lines would take about 3 hours. Let's tripple that to account for some more thinking, and we get about 1 day. So, I've actually spent about 1/20 th of my month actually writing code (about 20 work days in a month), or only 5% of the time. The rest of the time is spent compiling, debugging, planning, talking, and most of all figuring out what the hell the other code in the engine is doing and how I should interact with it and fixing bugs in it. Obviously, if we want to improve our efficiency as programmers, we should *not* work on ways of typing faster, but rather on ways of creating fewer bugs and making the processing of learning and reading code more important. When you have a job like mine which involves oversight of the entire project, you spend more time reading other peoples' code than writing your own. --------------------------------------------------------------------------------------- 1. COMMENTING & CLARITY Commenting is so obvious and important, there's no reason not to do it. It may take a little more time as you're working, but it'll end up saving hours if not days in debugging and frustration in the future. Comments are especially important when there is some strange "gotcha" or side-effects which are not obvious. Header files should be commented, with descriptions of each function describing its operation, and especially noting side-effects or inefficiencies. When you implement something and aren't sure about it, or know it's not quite right, you should mark it with a special comment. Also, if you do something lazily or inefficiently, you should comment that and also indicate so in the header. The key here is that you should think of every function you write as a "service" which is provided to the coders (even if that coder is you). Then you later want to use that service, you need to know how to use it and what it does, and how it will effect your code (eg. is it very fast? is it very slow? can it fail in a bad way? can it require user input?). Another important part of Commenting is writing your code in a way that comments itself. If you have some strange self-consistency requirements, add some assert()'s; they not only are useful for debugging, but provide documentation of the interrelationship of your variables. For example, if you have variables like 'counter' and 'counterModulo7' then you should sprinkle in 'assert( counterModulo7 == (counter%7) );' Another way to self-document code is through use of descriptive function names and variables. For example, small variable names like 'i' should only be used for loop counters that don't have any other meaning inside the loop. More ways of self-commenting code include the use of the 'const' directive which lets users know if a field will be modified (and also helps optimization), and by making complicated tasks have complicated names. For example, even though it's "natural" for a matrix to have an "operator *=" multiplication method, I choose not to implement it and make clients call "Multiply()", because I want to make it absolutely clear to users what they're doing when they perform something that expensive. A little more on the 'const' directive : in some compilers, const can improve optimization alot, because it lets the compiler know that the variable can be stored in any way. For example, if you have a variable which is on the heap (not the stack), the compiler cannot cache it out in a register unless it is const, because it must assume that any time you write to a pointer you might be writing to the memory where that variable is stored. Not 'const'-ing is also definitely contagious, because you cannot call a function which is improperly consted from a function which is consted (without casting). As a side note, when a class member function doesn't modify the "essence" of a class, then it should be declared as if the class were const, with internal casts when necessary. Also, whenever you cast to non-const to modify a value, use the const_cast<> to make it clear why you're casting. You may think this casting is uglier and requires more typing, and you're right, but it should - casting is evil and it should be very apparent to the eye and the fingers when it happens. Make functions minimal, and make them do only what they say. If you have a long function, it should probably be broken up into smaller functions which each have a specific task. This helps debugging (because you can test each function independently) and re-use, because those little functions may be useful elsewhere, as well as helping readability. Along with this goes the fact that the function can then be easily described by their name. The opposite of this is large functions that do lots of things as well as doing things that are not obvious, such as changing global or static variables. These result in bugs that are hard to track down. --------------------------------------------------------------------------------------- 2. MODULARIZATION Modularization is key to efficient development. It allows one programmer to work on a module of the code base without breaking or involving other sections. Basically, it lets your coding team work like a multi-processor machine; when the code base is not modular (eg. tangled up with dependencies) your coders synchronize, eg. can't work independently. Modularization is a large topic and more difficult than you may think. It hinges on good class design in C++. Classes should be a minimal implementation of their natural function. If a class is quite complex, perhaps it should be separated into a more fundamental base class and a derived class; put these in different headers so that other modules only need to include what they actually use. The class "interface", that is the public functions it provides, should not lock down it's implementation. For example, accessors that return the member variables are only slightly better than providing access to those variables directly. Which of course brings me to a point I perhaps glossed over : of course those member variables must be private, because making them public lets anyone use them, which locks down the implementation of the class indefinitely. Leaving classes the freedom to change is very very important. It lets you change your mind about the implementation later if you need to, which is almost always the case. For example, if you had an old 3d engine with a Mesh class which held lots of individual triangles with properties, you might now want to replace it with a Mesh that held a triangle strip - you cannot do that if the old variables are public, because your whole engine may be tangled up in accesses to that class. With a good class interface, you should be able to change the implementation without touching any of the code that depends on that class. In an ideal construction, that includes classes that derive from the one you change, but that may be impossible. Avoid Get() accessors, or at least discourage their use. Another part of modularization is simply splitting things into separate files and headers. This improves compile times (which is very important) by letting files only include the interfaces they really need. Note that hiding the implementation of classes also improves compile times. For example, any 3d engine should hide the API of the graphics architecture it's running on, so that only a few files in the engine actually need to parse "d3d.h" or "opengl.h" or whatever. Splitting things up also improves the parrallelism of work by making the source-sharing environment work better (CVS, SourceSafe, etc.). One nice way to acheive modularization is with helper classes and non-member helper functions. These are *not* friend functions or classes, which should essentially never be used, or use minimally, since "friend"s break modularization and encapsulation. Non-member helper functions for a class are functions which use only the public interface of a class, and automate common operations. Essentially, any manipulation of a class which happens more than once should go into a helper. The helper functions can be in a separate file and header from the main class. Similarly, any function in the class which could be a helper usually should (the except is functions which may reasonably some day need to be members if the class was implemented differently). Making helpers non-member functions help to minimize the class interface, which makes the class faster and more useful. It also makes it easier to modify and/or replace, since the core functionality is minimal and the non-member helpers need not be changed. You can use a namespace to wrap the non-member helpers. For example, you might have a class Vector and a namespace VectorHelper. Then you would do things like Vector v; VectorHelper::SetRandom(v); // which would use v.SetMembers(x,y,z); Helper classes are similar, but useful for larger tasks that have many sub-tasks. The helper class is constructed on an instance of the original class (not deriving, rather taking the original as a parameter) and does operations on it. For example, you might have an Image class. You could construct an ImagePainter class which would act on that Image. It would take functions like airbrush that drew into the image, but modify the Image data only through its public accessors. --------------------------------------------------------------------------------------- AN EXAMPLE Here's an actual example I just found of the bad code I used to be fond of writing. Let's find all the flaws. int countCharsSame(char *a,char *b) { int count = 0; while ( *a && *b ) { count += (*a++ == *b++); } return count; } This function counts the number of characters which are the same in the same location in two strings. The first problem is that I make use of the fact that 'bool' has value 0 or 1 when converted to an integer. That's a no-no : using pecularities of C (especially without commenting it), for no good reason. The next problem is that I didn't const the input pointers correctly. Next, the action of the function is un-documented; someone just seeing the name might not realize that characters must "line up" to be counted as matching. Finally, this method really should be a method of String which compares to another string (eg. it's not modularized; if you like using 'char *' for your strings you could just use a Str:: namespace). Here's a slightly better version : int String::CountCharsSame(const String & vs) const { int count = 0; for(int i=0; (*this)[i] && vs[i]; i++) { if ( (*this)[i] == vs[i] ) count ++; } return count; } Note that we've lost some efficiency; in particular, we've taken code that could be compiled into 'setge' and replaced it with a real branch. First of all, we can't take that last sentence too seriously until we look at the disassembly. Second of all, chances are this function is used rarely so clarity is more important than efficiency. --------------------------------------------------------------------------------------- ENFORCEMENT (or : how to make your programmers write code like this). This is a hard topic, because programmers are quite religious and sensitive about their coding styles and practices. They have lots of misconceptions about coding and cannot learn about them any way except through experience. First of all, don't get too specific too fast. Lead by example, encourage them to learn more about the programming practice, have them read suggestions like this document, but make it all a suggestion, not a requirement. Try to help them understand that you're working to improve the productivity of the team, not constrain them. Accept their give and take, help them find a set of coding guidelines that they can understand the reasons behind. Another part of encouraging by example is finding good code to show them. This means writing good code yourself, and preventing others from making it bad (and explaining to them why that change is not acceptable). This means finding good code that the team has written and pointing it out as an example, possibly with a reward for that coder. Positive reinforcement is always better than negative. Don't get hung up on specifics. Style issues are very personal and can make people very upset (I know, I used to get that way). In the end, it's just not that big of a deal, and it's not worth wasting time. Rather, work with the style that they prefer and try to point out changes you would suggest and why, such as "using tabs and spacing in your variable declarations to make it more clear which variable is of what type". Don't strictly enforce an indent and bracing style (unless the team will accept it, in which case uniformity is preferred). Let peer pressure do its work. Have the team do mutual code reviews. People will automatically write better code if they know someone else will examine it. This is part of the general principle that "the code is the product". Also, by having everyone review each other, they will naturally come to a more uniform style and standard. This can backfire if you have personalities that don't work together, so you have to be careful about who reviews who, and be sure to moderate the reviews (perhaps make them anonymous and route through you, but only do this in extreme cases). --------------------------------------------------------------------------------------- SPECIFIC TIPS X. use smart pointers They're great; auto_ptr in the STL is not (it's actually more of a trick for exception safety than anything else). X. write function test loops When you write a difficult new function, you should make a mini program which tests it by throwing all kinds of input at it and confirming the results. This mini program might be left in the main codebase so that you can run it again later and make sure your functions still work. This is part of the larger task of writing modules which are *reliable* so that clients (including yourself) can use them elsewhere and not have to worry about acquiring bugs from the old module. X. use assert() and do error checking I can never get enough of assert(). It's awesome for debugging; every function should be blanketed with enough asserts to make sure that the values that come into it are valid, and the values that come out are right. The best types of asserts are self-consistency checks (which verify some implicit relationship between the member variables of a class), and checks of a function's action by performing the same task a different way and comparing results. Along with this goes error checking. Any assert() which is not in a speed-critical location may need to also have a real error case for the release build. All user input and data files should be checked for consistency and handled with proper errors. I've worked places where this was not done, and I spent a lot of time looking for bugs in my code when it was actually an invalid program input that I was passing in and the old code wasn't checking for errors. ** I've talked to a lot of programmers who say "I don't need to assert here because the value should be what I expect". This is dead wrong and shows a big misunderstanding about how coding works. You must expect that the functions you call might be broken, therefore you must assert that they are doing something valid. You must also expect that the people who call your function will do it wrong, so you must assert on the parameters being valid. Anyway, how did they know how to call it right? Did you document the way the parameters must be valid? Probably not, so you better at least assert. Furthermore, I've heard people say "if this happens, the program will crash anyway, so why assert" - that's also wrong. The point of assert in these cases is to catch the crash as soon as possible, and to document why it crashes. Hitting an assert( ptr != null ) is much better than hitting an access violation. X. be careful about using Hungarian notation (Hungarian notation is the style in which you pre-pend variable names with a description of their type). Hungarian can be nice when used with discrimination. Used excessively, it forms a type of dependence on implementation. That is, if I wish to change the implementation of a class, I may need to change the types of its members, but may not need to rewrite every function that uses those members. For example, if you used "w" for WORD data and "dw" for DWORD data, and you needed to change a flag field from 16 to 32 bits, you would have to change every reference to "wFlags" to "dwFlags". Clearly this goes against the spirit of the notation and the ability to seamlessly change the underlying implementation. (see http://msdn.microsoft.com/library/techart/hunganotat.htm) X. no binary operators Binary operators like "operator +" require construction of a temporary. If you're defining operator +, it should only be on a mathematical class which is used in tight loops (like a Vector or Complex number). Thus, construction of temporaries cannot be tolerated (since most optimizers cannot eliminate constructors, even when they do nothing). Thus, you should only declare left-hand-modifying operators, like "operator +=". On a related note, some people think it's cool and good style to pass through the result of "operator +=" and "operator =". While it is true that passing through makes your operators equivalent to the ones on basic C types, like int, I don't really care to allow coders to do things like "a = b = c". Thus, I generally do not pass through the new value. X. use deferred declaration of variables; also use additional scoping Late declaration of variables (eg. right before they're used) provides optimization. Similarly, using scoping (that is, adding brackets around the lifetime of variables) provides optimization and helps prevent bugs. For example, a variable's lifetime should generally be explicitly terminated when it becomes invalid (eg. when you delete a pointer, let that pointer go out of scope, and also make sure any references to it go out of scope). Late declaration also improves clarity by letting the user see the type of the variable right near its use. Late declaration also helps when you comment out portions of code - you can easily comment out the variables used as well, instead of leaving them at the top. Finally, late declaration also helps force you to name things sensibly. Instead of having pMem at the top of your function, you can late declare pReadBuffer or whatever.. X. don't use int's declared in a for() elsewhere It's occasionally nice in C++ to declare the loop iterator right in the loop, like for(int i=0;i enum conversion will trip warnings. X. do not tread in the global namespace; in fact, namespace everything ! namespacing non-member functions is a good way to enforce modularity and wrap them together neatly. It also helps readability and helps find where those functions are coming from. Namespacing also prevents you from treading on other libraries' function names, which is an inevitable problem if you use class names like World,Engine,Executor,Object, etc. Everything should be in as small a scope as possible. See Herb Sutter for details. This is related to a somewhat more controversial topic : don't make public inner-classes (by inner class, I mean a class declared inside another. **** X. Local Function declaration This is a cool little trick for declaring a function in local scope : struct Local { void Func() ... }; Local::Func(); You can put this anywhere, and it provides great encapsulation. Not recommended if Func is more than a few lines. X. Class IsValid()'s All of my classes have a member called IsValid() which does sanity checking on the members. I check for totally invalid values (catches memory trashes) and also any internal logical consistency requirements ( like m_len == strlen(m_string) ). Then, classes can check the validity of anything passed into them by calling their IsValid's ; thus Matrix can check on the Vectors, etc. I do *not* like "GIGO" (garbage in-garbage out) - I insist on garbage in -> assert ! Also, anyone can be trashing your memory at any time, and IsValid calls all over is a great way to find these. X. Never copy code ! First of all, if you have the urge to copy some code, that probably means that that block should be made into a helper function or otherwise modularized better. Second of all, copying code is usually based on the bad assumption that the code you're copying is correct. Never assume that just because a chunk of code comes from a system that's functioning correctly that that means that that particular chunk of code is working correctly. There are *many* chunks of totally broken code embedded in working systems. X. Don't use macros! Convenience is a very low priority in coding, almost every other design principle is more important. Never use macros except when absolutely necessary (such as using __FILE__). What's wrong with macros? Multiple argument evaluation, can act like a function or a value or a complex expression, no type safety, no namespacing, can be redefined, no browse info, duplication of code at link time, miserable for debugging, etc. etc. Now, I'm not saying that macros have no place in reducing unnecessary typing, they do, just not at the expense of anything else. Macros for code are generally much worse than macros for declarations, because they produce code that's not debuggable. X. Avoid variable declarations, and make the variable names clear! Declaring variables for the convenience of how they're used is a bad idea. The usage will change, but the variable will stick around. Variables should have clear meanings. Variables should be minimal, in the sense of having minimal redundancy and consistency conditions between various variables; if there are consistency requirements, you must put them in the class IsValid() check. X. Writing code is very easy. I could write pages and pages of code if it didn't have to actually work. Here comes the mantra of "abilities" : Debugability Extensability Opacity Modularity Flexibility Readability Maintainability Making coding decisions based on ease of writing is a very bad idea. X. If process, code reviews, etc. are good ideas at any part of development, they are a good idea in crunch. Do NOT sacrifice process and rigor because of time constraints! The whole point of process is that it improves productivity and helps keep the code strong. X. Messy API encourage messy client code. X. Indirection is generally bad. Loose coupling is nice, but it should be easily resolved. By indirection, I mean something like having a VertexBuffer associated to you by name, so that you never see the VB. This make debugging much harder, it's inefficient, it's confusing, things tend to get out of sync, validity and consistency checking is much harder, etc. There is a common technique of using a map<> on an object pointer to add "side data" to that object; this is rotten; just put it in the object!! X. pass all references const, modifiables by pointer This is an optional rule, but it can be a good one. It follows the general principle I talked about in the text that "references are not always a good thing". In this case, it's not obvious to the caller that something being passed by reference will be modified (and clarity+transparency to the caller is very important). So, this rule says : anything which can be modified should be passed as a pointer, not a reference. A consequence of that is that all references should be const! So, you get functions like this : void func(const class & arg, class * arg); func(c1,&c2); X. virtual inheritance is interesting Virtual inheritance means that the parent data is accessed by a pointer instead of being embedded directly. This means that casting from a parent to a child cannot be done without dynamic_cast (usually, it's a c-style or reinterpret_cast). It also means that multiple instances of the same class as a parent are merged. The usual usage of this is with multiple inheritance where both parents come from the case base class. Without virtual inheritance, you get two of the base class, with it you only get one. Virtual inheritance is good for this even when you're deriving from abstract (pure virtual) base classes, because it means there's no abiguity about the cast-up. X. dynamic_cast is good. It's better than doing your own RTTI system. The advantages are : it's robust, obiquotus, standard, bug-free, and it's faster. For example, if you dynamic_cast two types that are known to related at compile time, then it compiles out to nada. Thus, I can implement a templated smart-pointer cast with dynamic_cast, and it will do CPU work only when actually necessary. There's also no added per-class-instance memory usage, since the dynamic_cast type info just gets in the vtable. X. Beware these common pitfalls : class X(); // is a function declaration ! class(); // is a construction of an unnamed class ! printf("%s\n",string("stuff")); // is totally invalid !! implicit enum to int conversions implicit bool to int and int to bool conversions the fact that a bool is not the same as a boolean floats in memory are not the same as floats in registers ---------------------------------------------------------------------------------------