You are on page 1of 9

Exercise

Write a function that calculates the frequency of a given character in a string, e.g. for string arjan and character a, the function would return 2 The function should work, but try to design it in the most ugly, incomprehensible, unreadable and inefficient way you can imagine

Game Programming in C++


Arjan Egges Lecture #5: The art of programming

Art vs. Science in Programming


After a certain level of technological skill is achieved, science and art tend to coalesce in aesthetic plasticity and form. The greater scientists are artists as well. (Albert Einstein)
Alice Calaprice, The New Quotable Einstein, Princeton.

Overview
Organising your code
File & directory structures Namespaces Comments Hungarian notation

Constness References Functions

Organising your code


Use a directory structure to group related classes:
#include <iostream> #include "graphics/GraphicsRenderer.h" #include "graphics/Mesh.h" #include "ai/Enemy.h" #include "ai/FiniteStateMachine.h" }

Organising your code


Another way to group code and classes: namespaces
Syntax:
namespace name { // put your code here

Organising your code


#ifndef CIRCLE_H_ #define CIRCLE_H_ namespace geom { class Circle { public: Circle(); float getSurface() const; protected: float radius_; }; } #endif }
Circle.h

Organising your code


#include "Circle.h" namespace geom { Circle::Circle() { radius_ = 1.0f; } float Circle::getSurface() { return 3.14 * radius_ * radius_; }
Circle.cpp

Organising your code


A few tips on using namespaces: When developing a toolkit, use a single namespace for all classes Only put using namespace statements in definitions (.cpp) and not in headers (.h)

Comments
Comments serve to clarify code and provide additional information to users, e.g. Provide comments for:
Header files containing functions and data structures For each data structure and function (with parameters, in/out and return values) Class descriptions All constructors/methods and the destructor Description of class attributes

Comments
Doxygen/Javadoc method http://www.doxygen.org Coherent documentation throughout your code Automatic generation of documentation (HTML, PDF, LaTeX, ) Automatic extraction of class structure (dependency graphs, inheritance structures, etc)

Comments
You can add a brief description and a detailed description:
/** * Brief description which ends at * this dot. Details follow here. */

Example:
/** * A constructor. * A more detailed description. */ Test();

Comments
/** * A normal member taking two arguments * and returning an integer value. * @param[in] a an integer argument. * @param[in] s a const character reference. * @see Test() * @see ~Test() * @see testMeToo() * @see publicVar() * @return The test results */ int testMe(int a, const char& s);

Comments
/** * A pure virtual member. * @see testMe() * @param[in] c1 the first argument. * @param[out] c2 the second argument. */ virtual void testMeToo(char c1,char& c2) = 0; /** * A public variable. * Details. */ int publicVar;

Hungarian notation
Invented by Charles Simonyi from Microsoft
Helps as a reminder of the type in the variable name Can be extended to include scope information in the variable name as well

Hungarian notation
Scope prefixes: m_ s_ g_ Class member variable, for example: m_HitPoints. Class static variable, for example: s_HeapName. Global variable, for example: g_UserInfo.

The book uses a subset of Hungarian notation

Hungarian notation
Type prefixes: b i f p Boolean variable, for example: bAlive. Integer, for example: iNumItems. Floating point, for example: fRatio. Pointer, for example: pItem.

Hungarian notation
Combination of scope and type prefixes:
bool m_bCanFly; static std::string* s_pName;

What are disadvantages of using this notation?

Const
const int MAX_PLAYERS = 4; MAX_PLAYERS = 2; // Compiler error! int * const int * const

Const
Const and pointers:
pData1 = new int(1); int * pData2 = new int(2); const pData3 = new int(3); int * const pData4 = new int(4);

Advantage of using const over #define: Const allows the compiler to apply C++ type-safety Const variables are entered into the symbol table and are thus available in the debugger

Const
Examples:
*pData2 = 5; // compiler error pData2 = pData1; *pData3 = 5; pData3 = pData1; // compiler error *pData4 = 5; // compiler error pData4 = pData1; // compiler error

Const
Pointers to const objects are frequently used in functions
// The Point3D object that pos points // to will not be modified. void Enemy::setPosition(const Point3D * pos);

Const
class Player { public: void setName(const char* name); char* getName(); private: char* name_[128]; }; char* Player::getName() { return name_; }

Const
A better way where we enforce that the returned pointer cannot be modified:
const char* Player::getName() { return name_; }

Or even better:
const char* Player::getName() const { return name_; }

Const
Defining const methods precise control over which methods update the object If you use this use it everywhere!
class SomeClass { public: void aMethod() const; void anotherMethod(); }; void SomeClass::aMethod() const { this->anotherMethod(); // compiler error }

Const
Disadvantages of using const Link with older libraries using const wrongly Sometimes member variables do not represent the status of the object
E.g. a variable maintaining some internal information, such as a counter for keeping track how often a method has been called.

Solution: Use the mutable keyword

Const
class Player { public: void setName(const char* name); const char* getName() const; private: char* name_[128]; mutable int getNameCounter_; }; const char* Player::getName() const { getNameCounter _++; // OK, because mutable return name_; }

References
References are most commonly used to pass function parameters:
// setRot takes a const reference to a new // rotation matrix void Joint::setRot(const Matrix4& rot) { if (!rot.isIdentity()) } // a matrix is expensive to copy Matrix4 rotation; // but we can call setRot without copying it! myJoint.setRot(rotation);

References
References can also be used as return objects:
const Matrix4& Joint::getRot() const { return rotation_; // cheap, just a ref } // Watch out, this makes a copy of the matrix! Matrix4 rotation = myJoint.getRot(); // This just holds the reference. Very cheap. const Matrix4& rotation = myJoint.getRot();

References
Advantages over pointers: Clearer syntax:
position = entity.getPosition();

As opposed to:
position = *(entity->getPosition());

References can never be NULL more difficult to pass an invalid reference No doubt on who is owner of object

References
Are also used to avoid copies of objects:
bool setActiveCamera(Camera c);

References
So are pointers obsolete?? Nooo. Sometimes pointers are still useful: When we want to transfer ownership of object When we want to change the object we point to (example: sorting) When we rely on the fact that pointers can be NULL Pointer arithmetic

More efficient:
bool setActiveCamera(const Camera& c);

The const part is not necessary for avoiding the copy, but it is there to indicate we dont modify the Camera object

Function calls
Functions form the basic components of programming
Analytic knife (Zen and the Art of Motorcycle Maintenance by Robert Pirsig) Each function type has a certain overhead

Function calls
Global/C functions:
int iHitPoints = GetHitPoints(gameUnit);

C++ offers different types of functions


Normal/global functions Class static functions Non-virtual member function Virtual member functions

Cost: jumping to a different memory location (almost negligable) General rule: forget about performance overhead and use functions whenever it seems logical:
Implementation in smaller sub-steps More readable code Easier to maintain code Encourages reuse (same sub-steps can be reused in other problems

Function calls
Class static functions:
Similar to global functions, but limited to a class Class static calls are handled in a similar way as global function calls by the compiler Therefore the same performance overhead as global function calls Provides a way to group related functions under a class for better understanding of code

Function calls
Nonvirtual member functions
Functions associated with a particular instance of a class
GameUnit gameUnit; int iHitPoints = gameUnit.getHitPoints();

Function address is determined at link time.


Type of object is known at compile time

Function calls
Nonvirtual functions
Implemented by having a hidden this parameter that points to the object begin called Internal representation:
GameUnit gameUnit; int iHitPoints = __GameUnitClass__getHitPoints(&gameUnit);

Function calls
Virtual member functions
Have potential to be the most expensive Occur when we invoke a method on polymorphic objects:

Example (runAI is a virtual member in the GameUnit class):


GameUnit* pGameUnit = new SomeGameUnit(); pGameUnit->runAI();

Additional performance cost: passing the extra parameter


Mostly, no final difference in performance

Function calls
Virtual member functions
Performance overhead for dereferencing the vtable:
GameUnit* pGameUnit = new SomeGameUnit(); (pGameUnit->vptr[3])(pGameUnit);

Function calls
Virtual member functions
Since each class has its own vtable, the depth of the inheritance hierarchy has no influence on performance When a virtual function is invoked directly on the object, same performance hit as normal function call:
GameUnit gameUnit; gameUnit.runAI();

This cost might become an issue if the function would be called for example a 1000 times per frame

Only one vtable per class


Can be stored anywhere in memory miss cache

But this goes through virtual function lookup:


GameUnit* pGameUnit = new GameUnit(); gameUnit->runAI();

Function calls
Virtual member functions:
Multiple inheritance appends vtable of parent classes Additional performance cost for offset to point to the correct section Using a large multiple inheritance tree could result in a large table
Higher cost for calculating correct offsets More data cache misses

Function inlining
class GameUnit { public: bool isActive() const; private: bool active_; }; bool GameUnit::isActive() const { return active_; } if (gameUnit.isActive())

Function inlining
Every time we call the function IsActive(), we will have the function overhead This is okay, but the function is quite trivial We could avoid additional performance cost by making active_ public:
if (gameUnit.active_)

Function inlining
It will get even worse if the function does a bit more than only return a member variable:
bool GameUnit::isActive() { return alive_ && running_; }

Now we need to expose both variables and we cannot guarantee the state of our instances Suppose we then decide that isActive should be calculated as follows:
(alive_ && running_) || selected_;

But now the user of our class needs to know about its implementation

Update all our code everywhere

Function inlining
Instead, use inline functions:
class GameUnit { public: bool isActive() const; private: bool active_; }; inline bool GameUnit::isActive() const { return active_; }

Function inlining
A second alternative of writing this:
class GameUnit { public: inline bool isActive() const { return active_; } private: bool active_; };

Function inlining
The compiler replaces the function with the code in the executable:
No function calling overhead, so more efficient execution

Function inlining
Why not always use inlining?
Executable size because of code duplication
Consumes more memory Poor use of code cache lower program performance

Inline functions must be declared in the header, not in the cpp file! When putting inline in front of a function, there is no guarantee the compiler will actually inline it!

Many includes move to the .h files, resulting in longer compilation times You lose the declaration/implementation separation

Avoid using inline when developing, but add inlining later when youre optimizing/finalizing your code Useful for small, frequently used methods like get-set methods

Summary
Organising your code (comments, notation, ) Const References Function inlining Next course: Design patterns

You might also like