C++ CPE common mistakes
C++ CPE · Understand the traps and how to avoid them.
Writing `void main()` or omitting the return statement in `int main()`, then wondering why the program fails to compile or returns an undefined exit status.
Candidates arrive from informal tutorials or other languages where main returns nothing; the C++ standard requires int main() and an implicit return 0 is only guaranteed for main, not for other functions, so habits blur together.
What to do instead
Always declare int main() and end with return 0; explicitly during practice until it is automatic. Remember that falling off the end of main returns 0, but any other non-void function that falls off the end has undefined behaviour.
Using `=` instead of `==` inside an `if` condition (e.g. `if (x = 5)`), so the branch always runs and the variable is silently overwritten.
The two operators look nearly identical, and in C++ an assignment is an expression with a value, so the code compiles without error and the compiler may only warn.
What to do instead
Compile with warnings enabled (-Wall -Wextra) and treat assignment-in-condition warnings as errors. Adopt the habit of writing comparisons as if (5 == x) in early practice, or use if (const int v = x; v == 5) style where a declaration makes intent explicit.
Reading input with `cin >> x` in a loop without checking the stream state, producing an infinite loop or garbage values when the user types a non-numeric token or reaches end of file.
operator>> sets the failbit on bad input and leaves the offending characters in the buffer; candidates assume extraction always succeeds and never test the loop condition against the stream.
What to do instead
Use while (cin >> x) { ... } so the loop terminates on failure, and know that cin.clear() plus cin.ignore(...) is needed to recover. For line-oriented input, prefer getline and then parse with std::stoi or a stringstream.
Mixing `cin >>` and `getline` and getting an empty string, because the newline left in the buffer is consumed by `getline`.
Formatted extraction stops before the delimiter and leaves it in the stream; candidates do not realise the leftover '\n' is a valid, immediate line for getline.
What to do instead
After a formatted read, discard the rest of the line with cin.ignore(numeric_limits<streamsize>::max(), '\n'); before calling getline, or read everything with getline and parse tokens yourself.
Declaring a function before use but forgetting that overload resolution depends on exact parameter types, then calling `foo(3)` expecting the `double` overload to be chosen silently.
Candidates learn that C++ converts numeric types freely in assignments and assume the same laxness applies to overload selection, missing that conversions rank candidates and can make a call ambiguous.
What to do instead
Study the conversion ranking rules (exact match, promotion, standard conversion, user-defined conversion) and deliberately test overload sets with int, long, double, and float arguments. When a call is ambiguous, add an explicit cast to state which overload you intend.
Passing a large object by value to a function and then being surprised that modifications inside the function do not affect the caller's object.
Beginners conflate the syntax of passing an object with passing a reference; without & the parameter is a copy, and the copy's destructor runs at function exit while the original is untouched.
What to do instead
Decide deliberately: use T& when the function must modify the caller's object, const T& for read-only access without copying, and plain T only for small types or when a copy is genuinely wanted. Write a two-line test that prints the object before and after the call to confirm which semantics you got.
Defining a class with a raw pointer member and letting the compiler-generated copy constructor and copy assignment perform shallow copies, leading to double deletion or use-after-free.
The rule of three is easy to state but candidates rarely see the failure until a copy is made, and the default-generated special members are invisible in the source.
What to do instead
Whenever a class owns a resource, either implement the destructor plus copy constructor and copy assignment (rule of three), or delete the copy operations and provide move operations (rule of five), or replace the raw pointer with std::unique_ptr/std::vector so the compiler-generated members are correct.
Forgetting `virtual` on a base-class destructor and then deleting a derived object through a base pointer, so the derived destructor never runs.
Polymorphism is taught with virtual functions but the destructor is often overlooked, and the bug is silent unless the derived class owns a resource.
What to do instead
If a class has any virtual function, make its destructor virtual (or protected and non-virtual if deletion through the base is forbidden). Verify with a small program that prints from each destructor when deleting through a base pointer.
Using `std::vector` iterators or references after an operation that reallocates the vector, such as `push_back` beyond capacity or `resize`.
Iterators are taught as safe handles to elements, and the invalidation rules depend on container and operation; candidates rarely check capacity() or read the invalidation table.
What to do instead
Memorise the invalidation rules for the containers you use: vector invalidates on reallocation, deque on push at either end, list/map/set only on erasure of the element. Call reserve when the final size is known, and re-acquire iterators after any mutating call.
Throwing an exception inside a constructor or destructor without handling it, or catching by value and slicing the exception object.
Exception safety is usually studied as syntax rather than as a design constraint; candidates do not consider that a destructor that throws during stack unwinding calls std::terminate, and that catch (Base e) copies only the base subobject.
What to do instead
Catch by const reference (catch (const std::exception& e)) to preserve the dynamic type and avoid copies. Keep destructors noexcept by default and swallow or log errors there. In constructors, release already-acquired resources before throwing, or use RAII members so their destructors clean up automatically.