C++ programming: Common Mistakes


๐พ Memory Management & RAII Mistakes
1. Raw Pointers Instead of Smart Pointers (Modern C++)
โ WRONG:
class BadClass {
private:
int* data;
public:
BadClass() : data(new int[100]) {}
~BadClass() {
delete[] data; // What if exception is thrown before this?
}
// Missing copy constructor and assignment operator!
// Will cause double delete or shallow copy
};
void bad_function() {
int* ptr = new int(42);
// Some code that might throw exception
delete ptr; // Might never be reached!
}
โ CORRECT:
#include <memory>
class GoodClass {
private:
std::unique_ptr<int[]> data;
public:
GoodClass() : data(std::make_unique<int[]>(100)) {}
// Rule of Zero: compiler-generated destructor is fine
// Smart pointers handle cleanup automatically
};
void good_function() {
auto ptr = std::make_unique<int>(42);
// Exception-safe: ptr automatically deleted
}
// For shared ownership
std::shared_ptr<int> shared_ptr = std::make_shared<int>(42);
2. Violating Rule of Three/Five/Zero
โ WRONG:
class BadResource {
private:
int* data;
size_t size;
public:
BadResource(size_t n) : size(n), data(new int[n]) {}
~BadResource() { delete[] data; }
// MISSING: Copy constructor, copy assignment, move constructor, move assignment
// This will cause double-delete when objects are copied!
};
void demonstrate_problem() {
BadResource obj1(100);
BadResource obj2 = obj1; // Shallow copy! Both point to same memory
// When obj1 and obj2 are destroyed, double-delete occurs!
}
โ CORRECT:
class GoodResource {
private:
std::unique_ptr<int[]> data;
size_t size;
public:
// Constructor
GoodResource(size_t n) : size(n), data(std::make_unique<int[]>(n)) {}
// Rule of Five (if you need custom behavior)
// Copy constructor
GoodResource(const GoodResource& other)
: size(other.size), data(std::make_unique<int[]>(other.size)) {
std::copy(other.data.get(), other.data.get() + size, data.get());
}
// Copy assignment
GoodResource& operator=(const GoodResource& other) {
if (this != &other) {
size = other.size;
data = std::make_unique<int[]>(size);
std::copy(other.data.get(), other.data.get() + size, data.get());
}
return *this;
}
// Move constructor
GoodResource(GoodResource&& other) noexcept
: size(other.size), data(std::move(other.data)) {
other.size = 0;
}
// Move assignment
GoodResource& operator=(GoodResource&& other) noexcept {
if (this != &other) {
data = std::move(other.data);
size = other.size;
other.size = 0;
}
return *this;
}
// Destructor (automatically handled by unique_ptr)
~GoodResource() = default;
};
// Better: Rule of Zero - use standard containers
class BestResource {
private:
std::vector<int> data;
public:
BestResource(size_t n) : data(n) {}
// All special members automatically generated correctly!
};
3. Memory Leaks with Exceptions
โ WRONG:
void bad_function() {
int* ptr1 = new int(10);
int* ptr2 = new int(20); // If this throws, ptr1 leaks!
// Some code that might throw
process_data(*ptr1, *ptr2);
delete ptr1;
delete ptr2;
}
class BadClass {
int* ptr1;
int* ptr2;
public:
BadClass() {
ptr1 = new int(10);
ptr2 = new int(20); // If this throws, ptr1 leaks!
}
};
โ CORRECT:
void good_function() {
auto ptr1 = std::make_unique<int>(10);
auto ptr2 = std::make_unique<int>(20); // Exception safe
process_data(*ptr1, *ptr2);
// Automatic cleanup, no explicit delete needed
}
class GoodClass {
std::unique_ptr<int> ptr1;
std::unique_ptr<int> ptr2;
public:
GoodClass()
: ptr1(std::make_unique<int>(10))
, ptr2(std::make_unique<int>(20)) {
// Exception safe: if ptr2 throws, ptr1 is already constructed
// and will be automatically destroyed
}
};
๐ง Object-Oriented Programming Pitfalls
4. Slicing Problem
โ WRONG:
class Base {
public:
virtual void print() { std::cout << "Base\n"; }
int base_data = 1;
};
class Derived : public Base {
public:
void print() override { std::cout << "Derived\n"; }
int derived_data = 2;
};
void bad_function() {
Derived d;
Base b = d; // SLICING! derived_data is lost
b.print(); // Calls Base::print(), not Derived::print()
std::vector<Base> vec;
vec.push_back(Derived{}); // SLICING! Objects are sliced when stored
}
โ CORRECT:
void good_function() {
Derived d;
Base& b = d; // Reference, no slicing
b.print(); // Calls Derived::print()
Base* ptr = &d; // Pointer, no slicing
ptr->print(); // Calls Derived::print()
// Use polymorphic containers
std::vector<std::unique_ptr<Base>> vec;
vec.push_back(std::make_unique<Derived>());
vec[0]->print(); // Calls Derived::print()
}
5. Missing Virtual Destructor
โ WRONG:
class Base {
public:
~Base() { std::cout << "Base destructor\n"; }
// Non-virtual destructor!
};
class Derived : public Base {
private:
int* data;
public:
Derived() : data(new int[100]) {}
~Derived() {
delete[] data;
std::cout << "Derived destructor\n";
}
};
void demonstrate_problem() {
Base* ptr = new Derived();
delete ptr; // UNDEFINED BEHAVIOR! Only Base destructor called
// Derived destructor never called -> memory leak!
}
โ CORRECT:
class Base {
public:
virtual ~Base() { std::cout << "Base destructor\n"; }
// Virtual destructor ensures proper cleanup
};
class Derived : public Base {
private:
std::unique_ptr<int[]> data; // Better: use smart pointers
public:
Derived() : data(std::make_unique<int[]>(100)) {}
~Derived() override {
std::cout << "Derived destructor\n";
// data automatically cleaned up
}
};
void good_function() {
std::unique_ptr<Base> ptr = std::make_unique<Derived>();
// Correct destructor sequence guaranteed
}
6. Improper Operator Overloading
โ WRONG:
class BadNumber {
int value;
public:
BadNumber(int v) : value(v) {}
// Wrong return type
void operator+(const BadNumber& other) {
value += other.value; // Modifies this object!
}
// Missing const
bool operator==(BadNumber& other) {
return value == other.value;
}
// Assignment returns void
void operator=(const BadNumber& other) {
value = other.value;
}
};
โ CORRECT:
class GoodNumber {
int value;
public:
GoodNumber(int v) : value(v) {}
// Binary operators should return new object
GoodNumber operator+(const GoodNumber& other) const {
return GoodNumber(value + other.value);
}
// Comparison operators should be const
bool operator==(const GoodNumber& other) const {
return value == other.value;
}
// Assignment should return reference for chaining
GoodNumber& operator=(const GoodNumber& other) {
if (this != &other) {
value = other.value;
}
return *this;
}
// Compound assignment operators
GoodNumber& operator+=(const GoodNumber& other) {
value += other.value;
return *this;
}
};
๐งต Template & Generic Programming Mistakes
7. Template Instantiation Issues
โ WRONG:
template<typename T>
class BadContainer {
private:
T* data;
size_t size;
public:
BadContainer(size_t n) : size(n) {
data = new T[n]; // What if T constructor throws?
}
~BadContainer() {
delete[] data; // Might leak if exception in constructor
}
T& get(size_t index) {
return data[index]; // No bounds checking!
}
// Missing template specialization considerations
void print() {
for (size_t i = 0; i < size; ++i) {
std::cout << data[i] << " "; // What if T doesn't support <<
}
}
};
โ CORRECT:
template<typename T>
class GoodContainer {
private:
std::vector<T> data;
public:
explicit GoodContainer(size_t n) : data(n) {}
T& get(size_t index) {
if (index >= data.size()) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
const T& get(size_t index) const {
if (index >= data.size()) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
template<typename U = T>
auto print() -> decltype(std::cout << std::declval<U>(), void()) {
for (const auto& item : data) {
std::cout << item << " ";
}
}
};
// SFINAE (Substitution Failure Is Not An Error) for better error messages
template<typename T>
class SafeContainer {
static_assert(std::is_copy_constructible_v<T>,
"T must be copy constructible");
static_assert(std::is_destructible_v<T>,
"T must be destructible");
// Implementation...
};
8. Template Argument Deduction Problems
โ WRONG:
template<typename T>
void bad_function(T value) {
// Problem: always copies, even for large objects
}
template<typename T>
class BadWrapper {
T data;
public:
BadWrapper(T value) : data(value) {} // Always copies!
};
void demonstrate_problems() {
std::vector<int> large_vector(1000000);
bad_function(large_vector); // Expensive copy!
BadWrapper<std::vector<int>> wrapper(large_vector); // Another copy!
}
โ CORRECT:
// Perfect forwarding
template<typename T>
void good_function(T&& value) {
// Use std::forward to preserve value category
internal_function(std::forward<T>(value));
}
template<typename T>
class GoodWrapper {
T data;
public:
template<typename U>
GoodWrapper(U&& value) : data(std::forward<U>(value)) {}
// Perfect forwarding in constructor
};
// Alternative: explicit overloads
template<typename T>
void alternative_function(const T& value) { /* for lvalues */ }
template<typename T>
void alternative_function(T&& value) { /* for rvalues */ }
void demonstrate_solutions() {
std::vector<int> large_vector(1000000);
good_function(large_vector); // No copy, passes by reference
good_function(std::move(large_vector)); // Move semantics
GoodWrapper wrapper(std::move(large_vector)); // Perfect forwarding
}
๐ Iterator & Container Mistakes
9. Iterator Invalidation
โ WRONG:
void bad_vector_operations() {
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it % 2 == 0) {
vec.erase(it); // UNDEFINED BEHAVIOR! Iterator invalidated
}
}
// Another problem
auto it = vec.begin();
vec.push_back(100); // May invalidate all iterators!
*it = 42; // UNDEFINED BEHAVIOR!
}
void bad_map_operations() {
std::map<int, std::string> m = {{1, "one"}, {2, "two"}, {3, "three"}};
for (auto it = m.begin(); it != m.end(); ++it) {
if (it->second == "two") {
m.erase(it); // Iterator invalidated, but loop continues!
break; // Must break immediately
}
}
}
โ CORRECT:
void good_vector_operations() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// Method 1: erase-remove idiom
vec.erase(std::remove_if(vec.begin(), vec.end(),
[](int n) { return n % 2 == 0; }), vec.end());
// Method 2: iterate backwards
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
if (*it % 2 == 0) {
vec.erase(std::next(it).base());
}
}
// Method 3: use erase return value
for (auto it = vec.begin(); it != vec.end();) {
if (*it % 2 == 0) {
it = vec.erase(it); // erase returns next valid iterator
} else {
++it;
}
}
}
void good_map_operations() {
std::map<int, std::string> m = {{1, "one"}, {2, "two"}, {3, "three"}};
// Method 1: find and erase
auto it = m.find(2);
if (it != m.end()) {
m.erase(it);
}
// Method 2: use erase return value (C++11+)
for (auto it = m.begin(); it != m.end();) {
if (it->second == "two") {
it = m.erase(it); // Returns next valid iterator
} else {
++it;
}
}
// Method 3: collect keys to erase (safest)
std::vector<int> keys_to_erase;
for (const auto& pair : m) {
if (pair.second == "two") {
keys_to_erase.push_back(pair.first);
}
}
for (int key : keys_to_erase) {
m.erase(key);
}
}
10. Incorrect Container Choice
โ WRONG:
void bad_container_usage() {
// Using vector for frequent insertions/deletions in middle
std::vector<int> vec;
for (int i = 0; i < 10000; ++i) {
vec.insert(vec.begin(), i); // O(n) operation each time!
}
// Using map when unordered_map would be better
std::map<std::string, int> word_count; // O(log n) operations
// for simple key-value lookups without ordering requirement
// Using list for random access
std::list<int> list = {1, 2, 3, 4, 5};
auto it = list.begin();
std::advance(it, 100); // O(n) operation!
}
โ CORRECT:
void good_container_usage() {
// Use deque for frequent front insertions
std::deque<int> deq;
for (int i = 0; i < 10000; ++i) {
deq.push_front(i); // O(1) operation
}
// Use unordered_map for simple key-value lookups
std::unordered_map<std::string, int> word_count; // O(1) average operations
// Use vector for random access
std::vector<int> vec = {1, 2, 3, 4, 5};
int value = vec[100]; // O(1) operation
// Use appropriate container for use case:
// - vector: random access, cache-friendly
// - deque: efficient front/back operations
// - list: frequent middle insertions/deletions
// - set/map: ordered data with fast search
// - unordered_set/map: fastest search, no ordering
}
๐ง Exception Safety Issues
11. Exception Safety Violations
โ WRONG:
class BadClass {
private:
int* ptr1;
int* ptr2;
public:
void unsafe_operation() {
delete ptr1;
ptr1 = new int(10);
delete ptr2;
ptr2 = new int(20); // If this throws, ptr1 is valid but ptr2 is invalid!
// Object is in inconsistent state
}
void another_unsafe_operation() {
int* temp = new int(42); // If this throws, object unchanged (good)
delete ptr1; // If destructor throws, temp leaks!
ptr1 = temp;
}
};
โ CORRECT:
class GoodClass {
private:
std::unique_ptr<int> ptr1;
std::unique_ptr<int> ptr2;
public:
void safe_operation() {
// Create new objects first
auto new_ptr1 = std::make_unique<int>(10);
auto new_ptr2 = std::make_unique<int>(20);
// Only assign if both succeed (strong exception safety)
ptr1 = std::move(new_ptr1);
ptr2 = std::move(new_ptr2);
}
void copy_and_swap_idiom(const GoodClass& other) {
// Create copy
GoodClass temp(other); // If this throws, *this unchanged
// Non-throwing swap
std::swap(ptr1, temp.ptr1);
std::swap(ptr2, temp.ptr2);
// temp destroys old data
}
};
12. Resource Leaks in Constructors
โ WRONG:
class BadResource {
private:
FILE* file1;
FILE* file2;
int* buffer;
public:
BadResource(const char* filename1, const char* filename2) {
file1 = fopen(filename1, "r");
if (!file1) throw std::runtime_error("Cannot open file1");
file2 = fopen(filename2, "r");
if (!file2) throw std::runtime_error("Cannot open file2"); // file1 leaks!
buffer = new int[1000];
// If this throws, both files leak!
}
~BadResource() {
fclose(file1);
fclose(file2);
delete[] buffer;
}
};
โ CORRECT:
// RAII wrapper for FILE*
class FileWrapper {
private:
FILE* file;
public:
explicit FileWrapper(const char* filename, const char* mode)
: file(fopen(filename, mode)) {
if (!file) {
throw std::runtime_error("Cannot open file");
}
}
~FileWrapper() {
if (file) fclose(file);
}
FILE* get() const { return file; }
// Non-copyable, movable
FileWrapper(const FileWrapper&) = delete;
FileWrapper& operator=(const FileWrapper&) = delete;
FileWrapper(FileWrapper&& other) noexcept : file(other.file) {
other.file = nullptr;
}
FileWrapper& operator=(FileWrapper&& other) noexcept {
if (this != &other) {
if (file) fclose(file);
file = other.file;
other.file = nullptr;
}
return *this;
}
};
class GoodResource {
private:
FileWrapper file1;
FileWrapper file2;
std::unique_ptr<int[]> buffer;
public:
GoodResource(const char* filename1, const char* filename2)
: file1(filename1, "r") // If this throws, nothing to clean up
, file2(filename2, "r") // If this throws, file1 auto-cleaned
, buffer(std::make_unique<int[]>(1000)) // If this throws, both files auto-cleaned
{
// Constructor body - all resources already acquired safely
}
// Destructor automatically generated - all RAII
};
๐ฏ Modern C++ (C++11+) Pitfalls
13. Move Semantics Misuse
โ WRONG:
class BadMoveExample {
private:
std::vector<int> data;
std::string name;
public:
// Wrong: should be noexcept
BadMoveExample(BadMoveExample&& other) {
data = std::move(other.data);
name = std::move(other.name);
// If this throws, other is in moved-from state but constructor failed
}
// Wrong: should be noexcept
BadMoveExample& operator=(BadMoveExample&& other) {
data = std::move(other.data);
name = std::move(other.name);
return *this;
}
void bad_usage() {
std::vector<int> vec = {1, 2, 3};
data = std::move(vec);
// Bug: using vec after move
std::cout << vec.size() << std::endl; // Undefined behavior!
}
};
โ CORRECT:
class GoodMoveExample {
private:
std::vector<int> data;
std::string name;
public:
// Correct: noexcept move constructor
GoodMoveExample(GoodMoveExample&& other) noexcept
: data(std::move(other.data))
, name(std::move(other.name)) {
// Member initializer list is exception-safe
}
// Correct: noexcept move assignment
GoodMoveExample& operator=(GoodMoveExample&& other) noexcept {
if (this != &other) {
data = std::move(other.data);
name = std::move(other.name);
}
return *this;
}
void good_usage() {
std::vector<int> vec = {1, 2, 3};
data = std::move(vec);
// Don't use vec after move, or check if it's in valid state
vec.clear(); // Reset to known state if needed
}
};
14. Auto and Type Deduction Issues
โ WRONG:
void auto_pitfalls() {
// Unexpected types
auto x = 1; // int, not long
auto y = 1.0f; // float, not double
std::vector<bool> vec = {true, false, true};
auto element = vec[0]; // Not bool! It's std::vector<bool>::reference
bool flag = element; // This can cause issues
// Dangling references
auto& ref = std::string("temporary"); // Dangling reference!
// Copy when you wanted reference
std::vector<std::string> strings = {"hello", "world"};
for (auto item : strings) { // Copies each string!
item += " modified"; // Modifies copy, not original
}
}
โ CORRECT:
void auto_best_practices() {
// Be explicit when type matters
auto x = 1L; // long
auto y = 1.0; // double
std::vector<bool> vec = {true, false, true};
bool element = vec[0]; // Explicit conversion
// Use auto&& for forwarding references
auto&& ref = std::string("temporary"); // OK, extends lifetime
// Use references in range-based for loops
std::vector<std::string> strings = {"hello", "world"};
for (auto& item : strings) { // Reference, no copy
item += " modified"; // Modifies original
}
for (const auto& item : strings) { // Const reference for read-only
std::cout << item << std::endl;
}
}
๐งฎ Numeric & Performance Issues
15. Integer Overflow and Undefined Behavior
โ WRONG:
void numeric_problems() {
// Signed integer overflow (undefined behavior)
int max_int = std::numeric_limits<int>::max();
int overflow = max_int + 1; // Undefined behavior!
// Array bounds issues
std::vector<int> vec(10);
int index = -1;
vec[index] = 42; // Undefined behavior!
// Uninitialized variables
int uninit;
int result = uninit * 2; // Undefined behavior!
// Null pointer dereference
int* ptr = nullptr;
*ptr = 42; // Undefined behavior!
}
โ CORRECT:
#include <limits>
#include <stdexcept>
void safe_numeric_operations() {
// Check for overflow
int max_int = std::numeric_limits<int>::max();
if (max_int == std::numeric_limits<int>::max()) {
throw std::overflow_error("Would overflow");
}
// Use checked arithmetic or larger types
long long safe_result = static_cast<long long>(max_int) + 1;
// Bounds checking
std::vector<int> vec(10);
int index = -1;
if (index >= 0 && index < static_cast<int>(vec.size())) {
vec[index] = 42;
} else {
throw std::out_of_range("Index out of bounds");
}
// Initialize variables
int value = 0; // Always initialize
int result = value * 2;
// Check pointers before use
int* ptr = get_pointer();
if (ptr != nullptr) {
*ptr = 42;
}
}
16. Performance Anti-patterns
โ WRONG:
void performance_problems() {
// Unnecessary copies
std::vector<std::string> get_strings();
std::vector<std::string> strings = get_strings(); // Copy
for (std::string s : strings) { // Copy each string
std::cout << s << std::endl;
}
// Inefficient string concatenation
std::string result;
for (int i = 0; i < 1000; ++i) {
result += std::to_string(i) + " "; // Multiple allocations
}
// Wrong container choice
std::vector<int> vec;
for (int i = 0; i < 1000; ++i) {
vec.insert(vec.begin(), i); // O(n) each time!
}
}
โ CORRECT:
void performance_optimized() {
// Move semantics
std::vector<std::string> get_strings();
auto strings = get_strings(); // Move if possible
for (const auto& s : strings) { // No copies
std::cout << s << std::endl;
}
// Efficient string building
std::ostringstream oss;
for (int i = 0; i < 1000; ++i) {
oss << i << " ";
}
std::string result = oss.str();
// Reserve capacity when known
std::vector<int> vec;
vec.reserve(1000); // Avoid multiple reallocations
for (int i = 0; i < 1000; ++i) {
vec.push_back(i);
}
// Use appropriate container
std::deque<int> deq; // Better for front insertions
for (int i = 0; i < 1000; ++i) {
deq.push_front(i); // O(1) each time
}
}
๐ก๏ธ Best Practices Summary
Modern C++ Guidelines
```cpp // 1. Prefer smart pointers over raw pointers std::unique_ptr ptr = std::make_unique(42); std::shared_ptr shared = std::make_shared(42);
// 2. Use RAII for all resources class FileResource { std::unique_ptr file; public: FileResource(const char* name) : file(fopen(name, "r"), &fclose) {} };
// 3
Subscribe to my newsletter
Read articles from Anni Huang directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Anni Huang
Anni Huang
Iโm Anni Huang, an AI researcher-in-training currently at ByteDance, specializing in LLM training operations with a coding focus. I bridge the gap between engineering execution and model performance, ensuring the quality, reliability, and timely delivery of large-scale training projects.