Pitfalls in decreasing order of their importance
First of all, you should visit the award winning C++ FAQ. It has many good answers to pitfalls. If you have further questions, visit ##c++
on irc.freenode.org
in IRC. We are glad to help you, if we can. Note all the following pitfalls are originally written. They are not just copied from random sources.
delete[]
on new
, delete
on new[]
Solution: Doing the above yields to undefined behavior: Everything could happen. Understand your code and what it does, and always delete[]
what you new[]
, and delete
what you new
, then that won't happen.
Exception:
typedef T type[N]; T * pT = new type; delete[] pT;
You need to delete[]
even though you new
, since you new'ed an array. So if you are working with typedef
, take special care.
Calling a virtual function in a constructor or destructor
Solution: Calling a virtual function won't call the overriding functions in the derived classes. Calling a pure virtual function in a constructor or desctructor is undefined behavior.
Calling delete
or delete[]
on an already deleted pointer
Solution: Assign 0 to every pointer you delete. Calling delete
or delete[]
on a null-pointer does nothing.
Taking the sizeof of a pointer, when the number of elements of an 'array' is to be calculated.
Solution: Pass the number of elements alongside the pointer when you need to pass an array as a pointer into a function. Use the function proposed here if you take the sizeof of an array that is supposed to be really an array.
Using an array as if it were a pointer. Thus, using T **
for a two dimentional array.
Solution: See here for why they are different and how you handle them.
Writing to a string literal: char * c = "hello"; *c = 'B';
Solution: Allocate an array that is initialized from the data of the string literal, then you can write to it:
char c[] = "hello"; *c = 'B';
Writing to a string literal is undefined behavior. Anyway, the above conversion from a string literal to char *
is deprecated. So compilers will probably warn if you increase the warning level.
Creating resources, then forgetting to free them when something throws.
Solution: Use smart pointers like std::unique_ptr or std::shared_ptr as pointed out by other answers.
Modifying an object twice like in this example: i = ++i;
Solution: The above was supposed to assign to i
the value of i+1
. But what it does is not defined. Instead of incrementing i
and assigning the result, it changes i
on the right side as well. Changing an object between two sequence points is undefined behavior. Sequence points include ||
, &&
, comma-operator
, semicolon
and entering a function
(non exhaustive list!). Change the code to the following to make it behave correctly: i = i + 1;
Misc Issues
Forgetting to flush streams before calling a blocking function like sleep
.
Solution: Flush the stream by streaming either std::endl
instead of \n
or by calling stream.flush();
.
Declaring a function instead of a variable.
Solution: The issue arises because the compiler interprets for example
Type t(other_type(value));
as a function declaration of a function t
returning Type
and having a parameter of type other_type
which is called value
. You solve it by putting parentheses around the first argument. Now you get a variable t
of type Type
:
Type t((other_type(value)));
Calling the function of a free object that is only declared in the current translation unit (.cpp
file).
Solution: The standard doesn't define the order of creation of free objects (at namespace scope) defined across different translation units. Calling a member function on an object not yet constructed is undefined behavior. You can define the following function in the object's translation unit instead and call it from other ones:
House & getTheHouse() { static House h; return h; }
That would create the object on demand and leave you with a fully constructed object at the time you call functions on it.
Defining a template in a .cpp
file, while it's used in a different .cpp
file.
Solution: Almost always you will get errors like undefined reference to ...
. Put all the template definitions in a header, so that when the compiler is using them, it can already produce the code needed.
static_cast<Derived*>(base);
if base is a pointer to a virtual base class of Derived
.
Solution: A virtual base class is a base which occurs only once, even if it is inherited more than once by different classes indirectly in an inheritance tree. Doing the above is not allowed by the Standard. Use dynamic_cast to do that, and make sure your base class is polymorphic.
dynamic_cast<Derived*>(ptr_to_base);
if base is non-polymorphic
Solution: The standard doesn't allow a downcast of a pointer or reference when the object passed is not polymorphic. It or one of its base classes has to have a virtual function.
Making your function accept T const **
Solution: You might think that's safer than using T **
, but actually it will cause headache to people that want to pass T**
: The standard doesn't allow it. It gives a neat example of why it is disallowed:
int main() {
char const c = ’c’;
char* pc;
char const** pcc = &pc; //1: not allowed
*pcc = &c;
*pc = ’C’; //2: modifies a const object
}
Always accept T const* const*;
instead.
Another (closed) pitfalls thread about C++, so people looking for them will find them, is Stack Overflow question C++ pitfalls.