Programming tips

This page contains several (more or less) general programming tips and tricks useful in creation of any project. They are targeted at both beginners and advanced programmers.

Majority of these tips results from practical experience.

Use constants instead of “magic numbers”

If you need to use a fixed number value somewhere in the program, it is almost always better to define a constant for it.

Bad

int points[100];
for (int i = 0; i < 100; ++i) { ... }

Good

// In case of usage in a class, 
// consider making the constant a static attribute.
const int NUMBER_OF_POINTS = 100;

int points[NUMBER_OF_POINTS];
for (int i = 0; i < NUMBER_OF_POINTS; ++i) { ... }

Code that works with an array or anything else is often spread across multiple files, therefore the second approach has several advantages:

  • smaller risk of typos
  • increased clarity
  • easier/faster changes at a later time

 

Passing a complex type as a function parameter

The following code segment works as expected but it is highly ineffective.

Bad

int getMaximum(std::vector<int> param) { ... }

When you call this function, the whole input vector will be duplicated and only its copy will be passed as a parameter. At the end of the scope, the copy will be deleted.

The main disadvantage is the fact that the function’s efficiency (and the amount of memory used to store the copy) depends on the size of the vector at run-time.

The recommended way is to use a pointer or a reference to the required type:

Good

int getMaximum(std::vector<int>& param) { ... }

Usage of the “auto” keyword

One of the additions to the new C++ standard, C++11, similar to the var keyword in C#.

Allows the programmer to avoid complicated type definitions and let compiler automatically determine the variable type. It can only be used if the type of the variable can be deduced from the assigned value.

// Create a new iterator specifying its type 
std::vector<int>::iterator iter1 = numbers.begin();

// Create a new iterator using the auto keyword
auto iter2 = numbers.begin();

// Usage in a for-cycle
for (auto it = numbers.begin(); it != numbers.end() ++it)
{
    // ...
}

Note: Support of C++11 features among the compilers varies. For example, the auto keyword is available in Microsoft Visual Studio 2010, GCC 4.4 and their later releases.

Inline methods

Many classes contain very short methods which are used to provide controlled access to the protected member attributes (get-set methods) or perform some other, basic operation.

The definition of these methods is often specified directly in the class header, e.g.:

class Mesh
{
private:
    std::vector<osg::Vec3> _vertices;
    ...
public:
    const osg::Vec3& getVertex(int index) const { return _vertices[index]; }
    ...
};

However, if we decide to cycle through all vertices and get their positions, the CPU will waste a very high amount of cycles managing the call stack and the execution might be (noticeably) inefficient. For this reason, it is better to use the inline keyword. The keyword will recommend the compiler to insert its code directly to places where it would normally be called.

This approach is of course far faster, even though the final code might be longer due to the duplication. It is therefore better to use it in case of shorter methods.

inline const osg::Vec3& getVertex(int index) const { return _vertices[index]; }

Saving intermediary results

Example of a very bad code:

for (int a = 0; a < getPolygonCount(); a++)
{
   glNormal3f(getVertex(getPolygon(a).v1).normal.x,
              getVertex(getPolygon(a).v1).normal.y,
              getVertex(getPolygon(a).v1).normal.z);
   glVertex3f(getVertex(getPolygon(a).v1).coordinates.x,
              getVertex(getPolygon(a).v1).coordinates.y,
              getVertex(getPolygon(a).v1).coordinates.z);
   glNormal3f(getVertex(getPolygon(a).v2).normal.x,
              getVertex(getPolygon(a).v2).normal.y,
              getVertex(getPolygon(a).v2).normal.z);
   glVertex3f(getVertex(getPolygon(a).v2).coordinates.x,
              getVertex(getPolygon(a).v2).coordinates.y,
              getVertex(getPolygon(a).v2).coordinates.z);
   glNormal3f(getVertex(getPolygon(a).v3).normal.x,
              getVertex(getPolygon(a).v3).normal.y,
              getVertex(getPolygon(a).v3).normal.z);
   glVertex3f(getVertex(getPolygon(a).v3).coordinates.x,
              getVertex(getPolygon(a).v3).coordinates.y,
              getVertex(getPolygon(a).v3).coordinates.z);
}

Main problem of the code above is that the getPolygon() function is called eighteen times with the same parameter and getVertex() is called six times with the same parameter.

If we save the intermediary result to a temporary variable, we will end up with shorter and more efficient code which is far easier to maintain.

Example of a good code:

Polygon *p = NULL;
Vertex *v = NULL;
for (int a = 0; a < getPolygonCount(); a++)
{
    p = &getPolygon(a);

    v = &getVertex(p->v1);
    glNormal3f(v->normal.x, v->normal.y, v->normal.z);
    glVertex3f(v->coordinates.x, v->coordinates.y, v->coordinates.z);

    v = &getVertex(p->v2);
    glNormal3f(v->normal.x, v->normal.y, v->normal.z);
    glVertex3f(v->coordinates.x, v->coordinates.y, v->coordinates.z);

    v = &getVertex(p->v3);
    glNormal3f(v->normal.x, v->normal.y, v->normal.z);
    glVertex3f(v->coordinates.x, v->coordinates.y, v->coordinates.z);
}

We may further replace variables v1, v2 and v3 by a fixed-sized array, which will produce even better code:

Polygon *p = NULL;
Vertex *v = NULL;
for (int a = 0; a < getPolygonCount(); a++)
{
    p = &getPolygon(a);
    for (int b = 0; b < 3; b++)
    {
        v = &getVertex(p->v[b]);
        glNormal3f(v->normal.x, v->normal.y, v->normal.z);
        glVertex3f(v->coordinates.x, v->coordinates.y, v->coordinates.z);
   }
}

It might seem that the code would be slowed down by the existence for-cycle, but it is not the case. The compiler will actually perform a loop unwinding optimization (thanks to the small and fixed number of cycles) so the cycle itself will not be present in the final code.

Iterator optimization

Slower variant

for (auto iter = vertices.begin(); iter != vertices.end(); iter++) { ... }

Faster variant

for (auto iter = vertices.begin(); iter != vertices.end(); ++iter) { ... }

In case of the post-increment operator, the value of the variable is increased by one but the operator is forced (due to the C++ specification) to return the previous value. Therefore a new temporary variable (copy of the iterator before the increment) has to be created, returned as a result – and immediately deleted since it’s not used.

On the other hand, the pre-increment operator increases the value of iterator and returns its value without a need to create a temporary variable – which yields a faster performance, especially in case of many elements.

Early exit on error (a.k.a. avoid nested code if possible)

This one is more of a recommendation – both snippets of code do the same computation. However, the first option results in a code which might be harder to comprehend and more prone to errors in case of later edits. There even might be some cases where the main code won’t fit on single screen due to heavy indentation.

It is therefore recommended to deal with any errors or invalid arguments as soon as possible and only put the code which performs the main function after all the necessary errors have been dealt with.

Before

void function()
{
    if (expression_1)
    {
        if (expression_2)
        {
            // Main
            // block
            // of
            // code
            // which
            // might
            // be
            // quite
            // long
        }
        else
        {
            std::cout << "Error 2" << std::endl;
        }
    }
    else
    {
        std::cout << "Error 1" << std::endl;
    }
}

After

void function()
{
    if (!expression_1)
    {
         std::cout << "Error 1" << std::endl;
         return;
    }

    if (!expression_2)
    {
         std::cout << "Error 2" << std::endl;
         return;
    }

    // Main
    // block
    // of
    // code
    // which
    // might
    // be
    // quite
    // long
}

Intentionally invalid variable values

If you have a variable which keeps a certain value, it is often useful to define a number that will represent an invalid value. This number is usually 0, -1, 0xFFFFFFFF, MAXINT or any other appropriate value (class std::numeric_limits might provide an inspiration) which is available in the given representation and the value itself either does not make sense or its occurrence is highly unlikely.

You may further store this value as a constant (e.g. as a constant member variable in a class)

#include <limits>

void function()
{
   const float INVALID_VALUE = std::numeric_limits<float>::infinity();

   // Rest of the code
   // ...
}

Of course, it is entirely possible to remember a “not filled”/”not initialized” state (e.g. bool isValid;), but the invalid value makes the code management simpler and decreases risk of errors caused by a of code which mistakenly changes only one of the variables.

Safer conditionals

Usage of a comparison operator == in conditional expressions always bears a risk of a typo which might result in an undesired usage of an assignment operator = instead. Assignment operator = returns the value of a variable on the left side as a result, so the error might be easily overlooked.

// Conditional
if (x == 3) { ... }

// In case of a typo, 3 is assigned to x which is then returned as a result
// => the expression is always evaluated as 'true'.
if (x = 3) { ... }

However, we may utilize the fact that the operator == is commutative and swap the expressions. This will allow us to easily detect any typo on a syntactic level – 3 is an r-value which doesn’t have a memory location and therefore cannot be assigned to. The typo will be immediately detected by a compiler and result in a syntax error.

// Equal conditional
if (3 == x) { ... }

// 3 is an r-value and cannot be assigned to
// => syntax error detected by a compiler
if (3 = x) { ...}

Memory allocation

While working with OSG classes, you should always use smart pointers (template class osg::ref_ptr).

If you allocate any memory which is not managed by smart pointers, you should always write new-delete in pairs. As soon as you allocate the memory, you should also write an appropriate block of code which will release it.

Moreover, bear in mind that the allocation process might fail (especially if you require a large amount of memory).

In case of a failure, operators new and new [] throw an exception of type std::bad_alloc which can be caught in a regular try-catch block.

If you don’t want to deal with exceptions, you may allocate the memory with an argument std::nothrow and check the pointer afterwards.

// Option #1 - catching std::bad_alloc
const unsigned int BUFFER_SIZE = 1000000;

try
{
    int * buffer = new int[BUFFER_SIZE * sizeof(int)];
}
catch(const std::bad_alloc&)
{
    std::cout << "Failed to allocate " << BUFFER_SIZE * sizeof(int) << " bytes." << std::endl;
    buffer = NULL;
}
// Option #2 - using std::nothrow
const unsigned int BUFFER_SIZE = 1000000;

int * buffer = new (std::nothrow) int[BUFFER_SIZE * sizeof(int)];

if(!buffer)
    std::cout << "Failed to allocate " << BUFFER_SIZE * sizeof(int) << " bytes." << std::endl;