C++: Difference between revisions
No edit summary |
|||
| Line 71: | Line 71: | ||
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. | 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 iostreams to printf and scanf=== | ===Prefer iostreams to printf and scanf=== | ||
| Line 102: | Line 104: | ||
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...."). | 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=== | ||
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=== | ===Don't typedef away indirection=== | ||
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 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=== | ===Don't reinvent the wheel=== | ||
Revision as of 17:41, 8 December 2009
Tips
Minimize includes in headers
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.
// 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
};
Now the header file using forward declarations instead of including another header file:
// 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
};
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
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 iostreams to printf and scanf
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. 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(2) << x << "\n";
Don't ignore compiler warnings
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
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
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
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 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
boost
-string manipulation - platform-independent file path handling - fixed-length integers (e.g 64 bit) See next section.
numerical especially important: lapack