C++

From bone.wiki
Jump to navigation Jump to search

C++ Development Tips

[edit]

Minimize includes in headers

[edit]

You should try to include the minimum number of other headers in your own header files. There are several reasons for this:

  • If all header files include many other headers files, which each in turn include many other header files, then compilation times can get very long. And if you change one header, you may suddenly find that practically your entire project needs to be recompiled.
  • By including many headers, which in turn include many other headers, you may be importing stuff you don't want and have no idea about. For example, you may be getting macro definitions that cause unexpected behaviour. This can be very hard to track to debug is there is a vast chain of headers to trace.

The alternative to including the header for functionality that you want to make use of, is to forward declare the objects, and then include the header in the .cxx file. (Including headers in .cc files is more innocuous, because .cxx files aren't in turn included by other files.

Here is an example. First the "before" case, where we have a certain type of header included.

<source lang=c>

// File "myClass.h" - example of including another header.
#include "vtkImage.h"

class myClass
{
public:
  myClass();   // Constructor
  ~myClass();   // Destructor
protected:
  vtkImage image;    // An image object in our class 
};

</source>

Now the header file using forward declarations instead of including another header file:

<source lang=c> // File "myClass.h" - example of using forward declaration. class vtkImage // Forward declaration

class myClass { public:

 myClass();   // Constructor
 ~myClass();   // Destructor

protected:

 vtkImage* image;    // An image object in our class 

}; </source>

Note that now we have a pointer instead of an actual instance of vtkImage. A forward declaration allows you to use only pointers and references to the foward-declared objects. This is because the compiler does not yet know how large the object is, but it does know how large a pointer is.

Now because we have only a pointer, we need to create an instance of our image object in the cxx file, and of course get rid of it too. One way to do this is in the constructor and destructor.

// File "myClass.cxx"

myClass::myClass()
{
  image = vtkImage::New();   // Create a vtkImage object
}
myClass::~myClass()
{
  image->Delete();
}

In this example we are using the VTK static New and Delete methods, because we have a VTK object. For other objects you will probably use the plain vanilla C++ new and delete commands.

Minimize use of macros

[edit]

Because your header file may be included in files created by other people, you absolutely want to minimize or eliminate the use of macros, because they could cause unexpected issues with someone else's code. C++ provides many ways to avoid using macros. For example

// A truely evil macro - never do this
#define max(x,y) (((x)>(y)) ? (x) : (y)))

In this case you can use a templated in-line function to achieve the same functionality with the same runtime speed:

template <typename T>
inline T max (T x, T y)
{
   return (x>y) ? x : y;
}

This version is typesafe, and can be overridden for specific types where required. It also illustrates another tip, which is #Don't reinvent the wheel, because there already exists a templated in-line max function in the C++ standard library.

Of course macros still do get used in C++ (for example, in debugging calls, because only macros can usefully use the compiler define __LINE__). But look hard for an alternative before resorting to a macro.

Prefer std::string to char arrays

[edit]

char arrays are terribly prone to buffer overruns and memory leaks. They are quite tricky to get exactly right, and the resulting code is often obtuse. Use std::string instead and let it manage the fiddly resource allocation issues for you.

Prefer iostreams to printf and scanf

[edit]

Most developers are familiar with the C-style printf statement syntax, for example:

printf ("The result is %d\n", i);

The drawback of this approach is that the formatting code depends on the type of i. Even worse, it may vary depending on the OS and whether we are using 32 bit or 64 bit. For example. maybe that formatting code should be "%ld" or "%lld" or "%Ld". The first is for long ints, the second for long long ints, and the third is system dependent. Which would you use for a 64 bit length integer? The answer to that is system dependent. Since we compile our code on many systems, and both as 32 bti and 64 bit, it is important to avoid this conundrum. (In templated code this situation is even worse).

The C++ iostream equivalent to the above is:

std::cout << "The result is " << i << "\n";

Now the compiler will make the right conversion for us, regardless of what i is. More complicated printf style formatting can also be converted to iostrings, for example:

printf ("The result is %.3f\n", x);

becomes

std::cout << "The result is " << setprecision(3) << x << "\n";

On the other hand, the standard C++ stream formatting modifiers like this example, although overloaded to correctly match the variable type, tend to be overly verbose to conveniently use.

I recommend therefore using boost::format where specific formatting is required. It is overloaded and will correctly match the variable type and length, but can be used with the familiar and concise POSIX format strings. boost::format has already been adopted in the next official C++ standard. Here is an example using boost::format:

std::cout << format("The result is %.3f\n") % x;

Don't ignore compiler warnings

[edit]

Often, your program will work correctly despite compiler warnings. Nevertheless, you should fix your code, if at all possible, so it does not generate any warnings. The warnings are there for a good reason. Very often they indicate a potential bug that could be hard to debug if it occurs. Other times, code that compiles and runs correctly, but with warnings, on one system, will compile but run incorrectly on another system. Finally, if your code generates many many warnings, you will cease to pay attention to them at all. New warnings, indicative of real problems, will then be lost in the noise.

Put comments in the appropriate place

[edit]

Comments that someone would need in order to make use of your class go in the header file. This includes documenting the purpose and use of each class method. (Example: "This method sets the file name.") Don't duplicate such comments in your .cxx file - it's hard enough to keep one set of comments up-to-date and when they inevitably get out of sync, no one will know which are current. Anyone reading the .cxx file will have to refer to the .h file anyways.

Comments that are about implementation details that don't affect usage should go into the .cxx file and only in the .cxx file. (For example "Here we make sure that we have enough memory allocated....").

Don't put 'using namespace' in a header

[edit]

For the same reason that you generally don't want to define macros in a header file. You don't want to force it on anyone who is using your header. There is presumably a reason why the objects were put into a namespace in the first place; using 'using namespace' puts the whole namespace into global namespace. It's fine however to use 'using namespace' in your .cxx file if you find that convenient, because it affects only your .cxx file.

Possible exception: 'using namespace std' is often seen in header files. I still don't think it's a good idea though.

Don't typedef away indirection

[edit]

This kind of thing was popular once:

typedef int* intP;
typedef int** intPP;

The reason was is it allows this kind of declaration:

intP A, B;  // A and B are both pointers to int

and avoids this mistake:

int* A, B;  // Oops, meant to declate A and B as pointers to an int, but B is actually declared int.

It's better to always declare one variable per line. Using typedef to hide indirection is not good practice, because other programmers actually need to know whether the thing in question is a pointer, or a pointer to a pointer, or whatever, in order to use in correctly. This is doubly important in C++, where there are two possible methods of indirection (pointers and references) and the rules for using them are different.

Don't reinvent the wheel

[edit]

Before writing any general-purpose routine, look around and see if it exists in a standard library somewhere. Besides saving yourself some trouble, standardized versions are typically more general, more efficient, and far less error-prone than your own custom solutions. Some places to look:

If you're not familiar with boost, here is a description from its homepage:

"Boost provides free peer-reviewed portable C++ source libraries.

We emphasize libraries that work well with the C++ Standard Library. Boost libraries are intended to be widely useful, and usable across a broad spectrum of applications. The Boost license encourages both commercial and non-commercial use.

We aim to establish "existing practice" and provide reference implementations so that Boost libraries are suitable for eventual standardization. Ten Boost libraries are already included in the C++ Standards Committee's Library Technical Report (TR1) and will be in the new C++0x Standard now being finalized. C++0x will also include several more Boost libraries in addition to those from TR1. More Boost libraries are proposed for TR2. "

Here are some areas where you should definitely NOT be writing you own custom tools:

  • string manipulation
  • platform-independent file path handling (use boost/filesystem)
  • fixed-length integers (e.g 64 bit). See next section.
  • parsing command line options and/or reading config files (use boost/program_options and/or boost/propertytree)

Fixed length integers

[edit]

Much of the code we write requires fixed length integers. There are many compiler-specific ways of specifying fixed-length integers. Commonly, people who want to use fixed length integers on many platforms have a custom header file with many #ifdefs and typedefs to handle many different compilers and platforms. Don't do this, it's not very robust. Instead, use only the C++ standard fixed length integer types.

So... what are the C++ standard fixed length integer types? The bad news is, there aren't any - yet. The new C++ standard, C++0x, does in fact include standard fixed length integers. However, not all compilers support C++0x yet (notably Microsoft compilers don't).

The best interm solution is to use cstdint.hpp from boost. This is identical to the C++0x standard, but works on any compiler.

Advanced Topics

[edit]

Forcing a C++ header to use C calling convention

[edit]

to do

[edit]

to do

Declaring functions for shared libraries on Windows

[edit]

to do

Getting a list of pre-defined macros for gcc

[edit]
touch dummy.hxx
cpp -dM ./dummy.hxx