Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What C++ pitfalls should I avoid? [closed]

Tags:

c++

stl

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.


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.


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)."