This topic seems obvious, but it isn't!! Many memory leaks errors derive from these errors!!
So, there are some rules you must consider:
- In polymorphic base class you must declare destructors virtual. If you don't, the results are undefined. Typically, the derived part of the object is never destroyed (memory leak). The same analysis applies to any class lacking a virtual destructor, including std::string and all the STL container types. You do never inherit from a standard container of from any other class with a non-virtual destructor!!
- Destructors should never emit exception. This situation can arise the problem of simultaneously active exceptions: the result is undefined. There are three primary ways to avoid the trouble:
- Terminate the program calling abort
- Swallow the exception (but it suppresses that something failed)
- Provide a regular function that gives the opportunity to react to problems that may arise (i.e., storing the result of destructor in a boolean variable)
- Have assignment operators return a reference to *this
- To make sure that a resource is always released, we need to put that resource inside an object (a resource manager) whose destructor will automatically release the resource. This idea is often called Resource Acquisition Is Initialization (RAII).
- A smart pointer SP is a wrapper for a resource (i.e., std::auto_ptr). It's destructor automatically calls delete on what it points to. Attention: copying auto_ptr set it to null and the copying pointer assumes sole ownership of the resource!
- A reference-counting smart pointer RCSP (i.e., std::tr1::shared_ptr) is a smart pointer that keeps track of how many objects point to a particular resource and automatically deletes the resource when nobody is pointing to it any longer. It also supports custom deleters: this prevents the cross-DLL problem and can be used to automatically unlock mutexes.
- SP and RCSP use delete, not delete[]. For arrays, look to Boost classes: boost::scoped_array and boost::shared_array.
- Initialize SPs in standalone statements (not in complex operations). Failure to do this can lead to subtle resource leaks when eceptions are thrown
- By default, C++ passes object by value:
void viewImage(Image i) {
i.view();
}
This is an expensive operation: function parameters are copies of the actual arguments! To bypass all related contructions and destructions, you have to pass by reference-to-const:
void viewImage(const Image& i) {
i.view();
}
Passing parameters by reference also avoids the slicing problem (when you don't use virtual function in polymorphic classes).
- It's instead more efficient to pass by value than by reference the following items:
- built-in types (e.g. an int)
- iterators in STL
- function objects in STL
- Prefer this way to write functions that return a new object:
inline const Image createImage(const Bitmap& bmp) {
return Image(bmp.getName(), bmp.getData());
}
It's faster than you expected and prevents many errors.
- Remember that you incur:
- the cost of construction when control reaches the variable's definition
- the cost of destruction when variable goes out of scope
You should postpone the definition until you have initialization arguments for it.
- In loops:
- it's more efficient define variables outside the loop and make an assignment to it on each loop iteration
- it's more readable define variables inside the loop. Unless you're dealing with a performance-sensitive part of your code, you should using this approach.
- Don't inline everything! Consider the rule of 80-20: a typical program spends 80% of its time executing only 20% of its code. You goal as a software developer is to identify the 20% of your code.
This work comes from the
Scott Meyers's book "Effective C++ - Third Edition"Very interesting the
George Belotsky's article.