A short list might be:
RAII, shared pointers and minimalist coding are of course not specific to C++, but they help avoid problems that do frequently crop up when developing in the language.
Some excellent books on this subject are:
Reading these books has helped me more than anything else to avoid the kind of pitfalls you are asking about.
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[]
onnew
,delete
onnew[]
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
ordelete[]
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;
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 ofDerived
.
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.
Some must have C++ books that will help you avoid common C++ pitfalls:
Effective C++
More Effective C++
Effective STL
The Effective STL book explains the vector of bools issue :)
Brian has a great list: I'd add "Always mark single argument constructors explicit (except in those rare cases you want automatic casting)."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With