C++ Resources, John Deacon

c++ Hometraps pitfalls style guide faq Resourcesjohn

Traps and Pitfalls

This began as an appendix to one of my C++ courses. I promised a couple of people that I would make it available online; the numbers are the page references for them (course notes version 2.5). It's also the first, and currently only, companion website resource for a book I am working on. This is a first draft (there is a change log below); I'm sure there are a few traps and pitfalls I've forgotten to put it; and the list will be revised; so by all means keep a bookmark or make a link, but please don't mirror the page.

This isn’t really a style guide; this document is rather too relentlessly and depressingly negative for that. Anyway, many, many excellent guides exist: one could start with the comp.lang.c++ FAQ, for example. However “Traps and Pitfalls” was the subtitle of the course from which it derives, and a summary was felt to be necessary.

The decision as to what to include here usually involved surprise. If a particular point would show up as a compile error or would simply require effort to understand hairy syntax then it was not typically included. If something would compile but produce wrong or possibly unexpected results then it was included.

Given that this began as an indexed appendix in some course notes, there is little explanation. (Framemaker's auto-appendix mechanism is brilliant but you're only allowed 255 characters.) The points are supposed to be aides memoire. If the points don’t make enough sense, and neither does reading the notes, for those who have them, try the comp.lang.c++ newsgroup's FAQ. And, although it’s not ready yet, more explanation will eventually be available in the book I am working on.

Obviously, there exists a wide spectrum of expertise in C++. These notes are meant for beginner- and intermediate-level C++ers; however, some of the more obvious traps have been marked as such. I’ve tried to root out duplication; apologies for any that remain.

I’ve almost certainly fallen into a few traps myself here. Do feel free to email me.

(A student once asked, “Why do you teach C++ if you dislike it so?” I don’t dislike C++ at all; I think it’s great fun. (But them I am the kind of person who likes to construct ships in bottles, out of matchsticks.))

Object-orientation is just one of the styles supported by C++. Don’t expect a great deal of help in adhering to any particular style. I-3

C++ is officially a general purpose programming language. While it offers support for object-orientation, it also offers support for structured, for generic, for mix-in and for no style at all. 1-3

C++ is officially based on C. C features – like name hiding – can interfere with C++ features like function overriding. 1-3

Just using classes doesn’t make one object oriented. Classes also figure in generic C++ and mix-in C++. 1-3

Function name overloading is convenient and good when appropriate; but confusing if misused. Be careful that overloading and name hiding don’t interfere. 1-3

References haven’t replaced pointers. Having both available makes it essential that one has a good conceptual model for differentiating them. 1-3

Primitive operands, arguments and returns get converted to their operators’ and functions’ expectations. 1-3

The presence of templates means that class member declarations involving a pointer to a template parameter might be interpreted as multiplication if you don’t disambiguate with "typename". 1-9

Because the default for both objects and primitives is to pass to and from functions by copy, copy constructors are called more often than one might first imagine. 1-13

The special functions will be used more than a sub-guru C++er would imagine. C++ makes copies and temporaries under several circumstances. Special functions have even more reason than usual to avoid side-effects and to be very careful about throwing.
  (And never throw from a destructor.) 1-14

C++ doesn’t take the attitude that incorrect primitive operand or argument types should be compile errors; it assumes you meant what you wrote and want the operands and arguments to be converted. 1-15

[Fairly obvious] The compiler doesn’t see your files; it sees the output of the preprocessor. 1-17

Some preprocessors are aged – very aged. They might not understand // comments. Expect only that they understand /* */ comments. 1-19

Although you can start an identifier with an underscore, you shouldn’t. Leading underscores are added to identifiers by C++ translators. 1-25

You might find bizarre things happening if you have an identifier like bitor or compl. They are alternative keyword forms of | and ~. 1-27

Tidying up columns of numbers with leading zeroes turns them into octal (base 8) numbers. 1-29

It’s not an error to have two character symbols within the single quote marks of a char literal (constant). 1-29

The encodings for char and wchar_t are not defined. Don’t assume ASCII or Unicode. 1-29

Literal floating-point values (constants) do not have type float; they have type double. 1-31

C++ is not one of those languages where, for example, one has functions (no side-effects) and procedures. In other words, what looks like an innocuous expression that computes a value, can cause non-local state changes. 2-6

Dividing by zero doesn’t result in an exception. It results in undefined behavior. You must check your divisors yourself. 2-9

You are allowed to perform addition and subtraction on pointer values. This can result in sensible behavior or in undefined behavior. It’s complicated. Read the text (and the standard). 2-11

Because assignment expressions are expressions and evaluate to values, and because values can frequently be converted to bool, accidentally using assignment where you meant comparison will almost always compile and fail silently at run-time 2-13

The order in which operands are evaluated is not defined. The fact that, for example, addition is defined to be left-associative, doesn’t necessarily mean that the operands of the addition operator are evaluated from the left. 2-15

The operands of logical and and logical or are evaluated from the left; however, there is a new uncertainty: not all the operands need be evaluated at all. 2-15
Variables can hold values or pointers; and this applies to objects of class type as well as primitives. Be careful you don’t assign to a pointer thinking it’s a value, or vice-versa. Consider "Hungarian naming". 2-17

E1 += E2, etc., are largely equivalent to E1 = E1 + E2, etc.; however, E1 is only evaluated once under +=, etc.. 2-17

Under assignment, when a pointer to an object is assigned another pointer to an object, they end up pointing at the same object. However, if an object-holding variable (or a reference to an object) is assigned another object, it becomes some kind of copy. 2-17

The increment (but not the decrement) operator can be applied to bool! It will set it to true whatever it was. 2-23

The precedence order of some operators – including the assignment operators and the conditional (arithmetic if) – was changed in ISO C++. 2-29

In general, blocks are good things. However, a declaration in a block within a block can hide a name from the outer block. And name hiding (an old feature) tends to interfere with clear OO (more recent features). 3-9

Whether you use curly brackets or not, selection and iteration statements control blocks. And the block begins at the opening parenthesis, not at the opening curly bracket (if there is one). 3-11

Selection and iteration statements should be controlled by boolean expressions, but it is not a compile error to put in numeric expression. 3-13
Although the (somewhat redundant) parentheses that hold the control logic are mandatory, curly brackets are not. Don’t leave traps for maintenance programmers – always put the curly brackets in. 3-15

The = symbol is actually the assignment operator, so it’s all too easy to say "if I can assign b to a" rather than "if a is equal to b". In C++, because numeric results can be converted to bool, such mistakes usually compiles, to fail silently later. 3-15

If you don’t put the curly brackets into an "if", it will be difficult to tell which "if" an "else" belongs to, if the "if" is, or ever becomes, nested. 3-17

The switch statement is really a slightly-tarted-up goto. This hints that maybe the switch should be avoided. 3-21

The switch statement involves just one block. The sections within are demarcated only with statement labels. You will need break statements after each (including the last – for maintainers) section. 3-23

Accidentally drop one of the colons (:s) of a scope resolution operator, and you might just end up with a labeled statement. 3-26

A switch gets exponentially more difficult to maintain. One new type to be handled can involve updating many, many switch statements. Contrast this with adding a new class behind a polymorphic type. Prefer object-orientation over switches. 3-27

Many, many examples of the for statement have i++ to increment the loop counter. If i is an int, it’s not really significant, but there’s always a chance that it might become an iterator, and then ++i is more efficient and here does just the same as i++. 3-33

The for’s init-statement (the first thing inside the parentheses) is scoped to the for’s block. In classic C++ it wasn’t. 3-33

[Fairly obviously] C++ has a goto statement. You just might have led a very sheltered life and not learned that using the goto results in near-infinite coupling [a very bad thing for non-trivial systems]. 3-40

A C++ compiler isn’t required to report an error if a value-returning function has no return statement. 3-41

(-PI <= theta <= PI) is always true, because the first comparison results in true or false which then convert to 0 or 1 which are both less than PI. In languages that don’t convert boolean the expression is thankfully a compile error. 4-15

[So well known, it’s hardly a trap but] short, int and long could all be the same size on one machine, and need not be the same size on any other machine. If you want portable code, you don’t use such built-in types; you use typedefs like size_t instead. 4-17

This is almost an anti-trap since consts are nearly always good. However, if you forget to make your query-only member functions const, then const objects of your class won’t be able to accomplish much. 4-23

Applying delete to a pointer doesn’t set it to zero; but you should probably consider doing so. 4-27

Primitive strings (C strings) are fraught with perils. Try not to use them. Consider objects of the string class instead. 4-35

However you declare the passing of a primitive string to a function, you will not pass a copy, but a pointer to the first character. 4-35

Primitive strings are one character bigger than you might think, since the end is indicated only by tacking on a zero-byte character. 4-35

Accidentally trashing the zero at the end of a primitive string sets up a disaster waiting to happen. 4-35

If you must use primitive strings, don’t process them yourself; learn to use the string processing library functions properly and stick to them. 4-35

Primitive arrays are not bound-checked. 4-39

[Not really a pitfall; more of an easy mistake] The index number of the last element of an n element array is n-1. 4-43

Incorrectly accessing an array of arrays (when simulating a multi-dimensional array) as arr[i, j] instead of arr[i][j] is not a compile error even though it won’t do what you probably expect. 4-43

References create aliases for identifiers. Be careful not to end up doing daft things like self-assignment. Self assignment might go horribly wrong for classes with badly-written copy assignment operators. 4-45

[To avoid setting up your own mental traps] Keep repeating, "Anything I appear to be doing to a reference, I am actually doing to the thing it’s referencing." 4-46

While a reference might be a pointer behind the scenes, 95% of the time you shouldn’t think of it as a pointer. Don’t try to check for null references, for example; such things can’t exist. 4-47

Do not return via references unless you are absolutely sure that you know exactly what you are doing. (And make sure you know if and how your compiler supports the return value optimization.) 4-53

Don’t try to be tidy by including empty parentheses when initializing object in variables via the default constructor. You will actually declaring parameterless functions instead. 4-65

Beware a course or book that tells you that classes are for making objects. It’s much more complex than that – classes are used for three purposes in OO C++, and could be used for at least another three outside of that. 5-3

Class definitions (typical contents of object-oriented header files) are declaration statements and must end in ";"s Forgetting this usually leads to an impenetrable error message in another file. 5-19

Don’t think of public as meaning the same for data and function members. Public data members can be accessed; but public member functions can’t be called. Public member functions define the signatures of messages that all other objects can send. 5-27

Unlike, say, Smalltalk, C++ has class-level encapsulation. An object isn’t prevented from accessing the private data members of another instance of its class.
  Apart from the copy constructor and the copy assignment operator, don’t allow one instance to access the data of another instance. 5-29

C++ doesn’t enforce the private access category for data members. One day you’ll be tempted to make a data member of a base class protected. Resist. Don’t do it. From that moment on, the base class becomes unmaintainable. 5-33

If you think about it, it would have to be a very clever compiler that could apply the inline optimization to a function that hadn’t been #included. So for most situations today, the inline keyword in a .cpp (.cxx) file is a waste of time. 5-42

It’s not mandatory to give formal parameter names in header files, so it’s easy to have parameter declarations that are not self-documenting. 5-45

Object instance arguments are passed by value, i.e. by copying. But forget to provide the copy constructor and one will be provided for you. And Finagle’s Law says that anytime you do forget, the implicit copy constructor’s behavior won’t be right. 5-47

Arrays (like char[], mentioned earlier) are not passed to functions by the normal, by-value, mechanism. Instead, a pointer to the first element is passed. 5-49

Forgotten parentheses on an argumentless message or call will often compile because a function’s address is a valid numeric expression. 5-51

Const objects can change. While passing out a pointer (or reference) to a data member is hardly a subtle trap, data members can be declared mutable however; so we hope that such data member have been designed to be undetectable from outside their object. 5-57

Static data members are close to global. Static member function uses are close to calls (rather than messages). Overusing static members takes one away from object-orientation and towards the less useful class-orientation. 5-59

It’s possible to create systems where it becomes almost impossible to say which function will be used. Strive to avoid mixing the possibilities of overloading, argument conversions, templates and default parameter values. 5-67

Never mix up the two initialization syntax schemes of "=" and "()". Try to always use ()-style initialization. Never, ever allow the initialization of an object of class type to involve = and () at the same time. 5-68

Objects get initialized, which is good. It does mean, however, that an array of n objects isn’t an array with space for n objects, it’s an array with n default constructed objects already there. 6-9

The C memory mechanisms malloc and free still exist in C++, of course. It’s not a compile mistake to free() something newed or to delete something malloc()ed; just lethal. 6-15

It’s not a compile error to "delete" (rather than "delete[]") something that was "new []"ed. It’s almost certainly a memory problem though. 6-17

[So famous it’s hardly a trap] The C++ system doesn’t reclaim unreferenced free store (doesn’t do "garbage collection"). The programmer must return free store memory when it’s no longer required. Copied pointers make this a very challenging aspect of C++. 6-21

To realize how quickly it could become impossible to decide who takes out the garbage, one only has to recall that the default argument passing and return mechanisms are by-copy; and that the implicit copy constructor does shallow copy. 6-21

Looking for smart pointers to help with garbage collection and with polymorphic collections, one turns to the library. But the smart pointer one finds there – auto_ptr – mustn’t be used for either of those purposes. 7-9

Don’t pass an auto_ptr to a function by value. The auto_ptr will be copied and the copy will become the owner When the function finishes, the local copied auto_ptr goes out of scope and it will delete what it was pointing at. 7-9

Almost uniquely among languages today, C++ allows one to store entire objects in variables. This leads to no end of complications; and certainly leads to the special functions being special. 8-3

A class without constructors may well have implicit constructors. There are only two circumstance when an implicit copy constructor wouldn’t be provided; and only a few more where a default constructor wouldn’t. And the implicit ones are public. 8-11

People sometimes try to call one constructor from another. Don’t. You can write something that looks like it might just be a call to a constructor but it won’t be. Constructors are always declaration statement stuff rather than expression statement stuff. 8-14

A one-argument constructor will be implicitly used as a conversion function (argument type to class type) unless you say not to – with the "explicit" keyword. And constructors with default parameter values might be also be considered single-argument. 8-15

Another reason not to use the = sign in declaration/initialization is to avoid encouraging the mistaken belief that the copy assignment operator would be used. It wouldn’t be. The copy constructor will be used for both = style and () style initialization. 8-18

The implicit copy constructor effectively does shallow copy – it copies pointers but not pointees. 8-19

Lulled into complacency by constructor chaining, you might imagine that the copy assignment operator automatically chains as well. It doesn’t. You will probably want to though. 8-26

[Widely known but] Primitives, including pointers, have random initial values for all practical purposes (since we won’t be using static storage much, will we). 8-31

Data members holding objects of class type are default constructed before the constructor’s code block runs. Initialize them via the member initialization list instead. 8-33

If you initialize an object of class type via {} expression lists, almost any change to the class will invalidate such an initialization. 8-37
The order of initializers in a member initialization list is irrelevant. Don’t try, in a member intiailzation list, to initialize one member in terms of another unless you know the initialization order rules. 8-43

Although destructors can be called explicitly, it is an exotic and probably dangerous thing to do. Be happy with automatic calling of destructors. 8-45

The implicit destructor is non-polymorphic (non-virtual). Just about everyone, just about all the time must consider whether their destructor should be polymorphic (virtual); and probably conclude that it should be. 8-49

You might not think you need a destructor, but unless you can prove that your class could never be a base class, you should provide a virtual (not pure virtual) destructor, even it is has an empty code block. 8-51

Overloaded operators must keep the precedence of the built-in operator. So trying to use ^ for an exponentiation operator will give counter-intuitive precedence, i.e. will always require parenthetical disambiguation. 9-7

Remember that neither the number of arguments nor their position contribute to the "name" of a member function. So an overload of unary +, say, in a derived class would hide a binary + in a base class. 9-8

Operator overloads are inherited but the implicit copy assignment operator usually hides any base class copy assignment operator. 9-13

You can’t make && or || (or ,) operator overloads work intuitively, so don’t provide them. (You can’t mimic guaranteed operator evaluation order or lazy evaluation with member functions.) 9-29

You might be tempted to provide operator bool in order that your objects can easily be tested for validity. But beware that once your objects can be converted to bool, they can also be converted to int. 9-34

[Well known but] Not only is int converted to long, for example, but long is converted to int, and float is even converted to int. If you’re lucky, a friendly compiler will issue a warning. So treat C++ compiler warnings as though they were errors. 10-11

Inheritance of implementation has not turned out to be the labor-saving device we thought it would be. (Unlike composition, it doesn’t function through low-coupling, program-by-contract messages.) 11-3

While the maintainability of the derived classes goes up because of factoring, the maintainability of the base class rapidly goes down. So be sure your code is stable before you start introducing inheritance of implementation. 11-3

Given the fragility of base classes, they should always be destined and designed to be base classes. A class should never just slip into becoming a base class. Design your concrete, derived classes so that they could never become base classes. 11-3

So why don’t we ban inheritance? Because there’s something more important than implementation to inherit. Public inheritance gives objects extra types; it supports polymorphism. 11-5

Private data members are present in the instances of derived classes. They just can’t be accessed by derived class code (which is a good thing). 11-7

Beware style guides that say multiple inheritance mustn’t be used. It’s OK for an object to inherit multiple types, but it’s way more trouble than it’s worth for an object to inherit implementation from more than one base class.
  So a class can have multiple base classes if all, or all bar one, of those base classes are pABC (purely abstract base classes). 11-9

The class keyword brings private access for the members. But that includes inheritance; and private inheritance is not the object-oriented way. Private inheritance is the (now not very popular) mixin way. 11-11

Don’t be trapped into mentioning base class names any more than you have to. Use the "typedef BaseClass super" trick to avoid it. And don’t use any class name as part of a function name. 11-20

[As mentioned earlier] Beware if you think you’re overloading a member function from a base class. A derived class member function with the same name as a base class member function name-hides the base class one. A using declaration can sort this out. 11-21

Unless you use virtual member functions, C++ doesn’t give you polymorphic behavior. By default C++ uses the pointer type to select member functions. That way lies redundant overriding and inconsistent binding. 11-23

Don’t be misled into thinking that there’s anything "ghostly" or "not really there" about virtual member functions. Pronounce "virtual" as "polymorphic". (The ghostly ones are the pure virtuals.) 11-25

It’s not a compile error to forget to pass a derived class object by pointer or reference, when the parameter declaration was for a base class object. What will happen is that the derived bits will be "sliced" off as the argument is copied onto the stack. 11-26

Although making a member function polymorphic (virtual) means that the object selects the member function, it’s still the pointer type that selects the access category.
  If you override a member function in a different access category, a) we hope you’ve done it to be more restrictive, not less, and b) the type of the pointer chooses the access category determining class. 11-35

Don’t be misled by the unforgivably obscure syntax (= 0) into thinking that pure virtual member functions are some dark and dusty corner that is to be ignored. They are pivotal to good OO. 11-37

Don’t imagine that a class with no code and no data members is worthless. A class with nothing but pure virtual member functions (a pABC) is an excellent and simple type that classes can implement. 11-39

Don’t by misled by some books (or even libraries and frameworks) into thinking it’s OK to instantiate base classes. It’s almost never a good idea – you end up with a class doing three jobs, and two is bad enough. 11-39

If you disregard the earlier advice to allow one or none of your base classes to have implementation, you will also and always have to consider whether your inheritance should be virtual inheritance. 11-45

Don’t imagine that because the RTTI (run-time type identification) was introduced, you should use it. Treat "What’s your class?" as a rude question and use it only once in a blue moon.
  Overuse of the RTTI probably indicates an architecture that is failing – not having enough 1:m association relationships or the 1:m association relationships having the wrong types. 12-5

It’s not an afterthought (I hope) that the type_info object supports one in avoiding mentioning a class by name. Avoid building class names into anything but declarations. And even in declarations avoid concrete class names as types. 12-5

Putting "using namespace std" makes a very large set of unseen names available to clash in "what on earth’s going on" kinds of ways with your identifiers - particularly at some point in the future. 14-13

There might be more namespaces than you thought in which C++ will look for a function. C++ also searches for a function in the namespaces in which any of a function’s arguments of class type are defined. 14-21

If any function you call (or that it calls, or ...) could throw, you need to be an order of magnitude more careful a programmer than you would otherwise have needed. 15-11

A derived object catcher that follows a catcher for its base class can never be reached. 15-17

Although you don’t have to catch exception objects by reference, getting into the habit of doing anything else is asking for trouble. See bit slicing. 15-18

Exception specifications are not checked at compile time. Java programmers beware. A function with an exception specification that tries to emit an undeclared exception at run time will cause the program to terminate. 15-19

Change log (ISO date format -- yyyy-mm-dd)

2005-03-31: first draft made available.