What C++ pitfalls should I avoid? [closed]
I remember first learning about vectors in the STL and after some time, I wanted to use a vector of bools for one of my projects. After seeing some strange behavior and doing some research, I learned that a vector of bools is not really a vector of bools.
Are there any other common pitfalls to avoid in C++?
Solution 1:
A short list might be:
- Avoid memory leaks through use shared pointers to manage memory allocation and cleanup
- Use the Resource Acquisition Is Initialization (RAII) idiom to manage resource cleanup - especially in the presence of exceptions
- Avoid calling virtual functions in constructors
- Employ minimalist coding techniques where possible - for example, declaring variables only when needed, scoping variables, and early-out design where possible.
- Truly understand the exception handling in your code - both with regard to exceptions you throw, as well as ones thrown by classes you may be using indirectly. This is especially important in the presence of templates.
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:
- Effective C++ - Scott Meyers
- More Effective C++ - Scott Meyers
- C++ Coding Standards - Sutter & Alexandrescu
- C++ FAQs - Cline
Reading these books has helped me more than anything else to avoid the kind of pitfalls you are asking about.
Solution 2:
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[]
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;
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 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.
Solution 3:
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 :)
Solution 4:
Brian has a great list: I'd add "Always mark single argument constructors explicit (except in those rare cases you want automatic casting)."
Solution 5:
Not really a specific tip, but a general guideline: check your sources. C++ is an old language, and it has changed a lot over the years. Best practices have changed with it, but unfortunately there's still a lot of old information out there. There have been some very good book recommendations on here - I can second buying every one of Scott Meyers C++ books. Become familiar with Boost and with the coding styles used in Boost - the people involved with that project are on the cutting edge of C++ design.
Do not reinvent the wheel. Become familiar with the STL and Boost, and use their facilities whenever possible rolling your own. In particular, use STL strings and collections unless you have a very, very good reason not to. Get to know auto_ptr and the Boost smart pointers library very well, understand under which circumstances each type of smart pointer is intended to be used, and then use smart pointers everywhere you might otherwise have used raw pointers. Your code will be just as efficient and a lot less prone to memory leaks.
Use static_cast, dynamic_cast, const_cast, and reinterpret_cast instead of C-style casts. Unlike C-style casts they will let you know if you are really asking for a different type of cast than you think you are asking for. And they stand out viisually, alerting the reader that a cast is taking place.