Introduction
This document summarizes essential C++ concepts including syntax, memory management, and object-oriented programming.
1. C++ Fundamentals
1.1 Pointers and References
Differences Between Pointers and References
A pointer stores the address of an object. Its itself a variable (a named object) and has its own address, allowing pointers to pointers to exist. Pointers are mutable, meaning both the address they hold and the data at that address can change. A reference is an alias for a variable; it must be initialized and cannot be reassigned to refer to a different object.
-
Definition and Declaration A pointer is a variable whose value is the address of another variable. Use the
*symbol when declaring a pointer. A reference is an alias created for an existing variable. Use the&symbol when declaring a reference. -
Usage and Operations Pointers use the dereference operator
*to access the value of the variable they point to, and the address-of operator&to get a variable's address. References are initialized upon declaration and refer to the same variable throughout their lifetime. No dereference operator is needed because a reference is the variable's alias. Pointers can have multiple levels (e.g., pointer to pointer), while references have only one level. -
Null Values A pointer can be null (
nullptr), indicating it points to no valid address. A reference must be initialized upon declaration and cannot be rebound. There is no concept of a null reference. -
Mutability A pointer's target can be changed to point to a different memory address. Once a reference is initialized, it always refers to the same object and cannot be rebound.
-
Use Cases Use pointers when you need to return the memory of a local variable inside a function. Dynamic memory allocated with
newmust be explicitly freed to avoid memory leaks. Returning a reference to a local variable is meaningless. Use references when stack space sensitivity is important (e.g., recursion). Passing by reference avoids creating temporary objects, incurring less overhead. Use references when passing class objects as parameters; this is the standard idiom in C++.
Function Pointers
Definition and Usage
A function pointer is a variable that stores the address of a function. It enables dynamic selection of which function to call at runtime.
The syntax is: return_type (*pointer_name)(parameter_list)
Assignment methods: pointer_name = function_name; or pointer_name = &function_name;
int sum(int a, int b) {
return a + b;
}
int diff(int a, int b) {
return a - b;
}
int main() {
int (*func_ptr)(int, int);
func_ptr = ∑
int res = func_ptr(12, 5);
std::cout << "Result: " << res << std::endl;
func_ptr = &diff;
res = func_ptr(12, 5);
std::cout << "Result: " << res << std::endl;
return 0;
}
Use Cases:
- Callbacks: passing function addresses to other functions for later invocation.
- Function pointer arrays: implementing state machines that call different functions based on input.
- Dynamic library loading: calling functions from dynamically loaded libraries.
- Polymorphism: combining virtual functions with function pointers for polymorphic behavior.
- Function pointers as parameters: enabling pluggable function behavior.
- Function mapping tables: mapping conditions to specific function calls.
Function Pointer vs. Pointer Function
A function pointer is a pointer variable that points to a function.
int sum(int a, int b) { return a + b; }
int (*fptr)(int, int) = ∑
int val = (*fptr)(3, 4);
A pointer function is a function that returns a pointer.
int* fetchPointer() {
int x = 10;
return &x; // returning address of local variable is not recommended
}
1.2 Data Types
Integer Types: short, int, long, long long
C++ integer type length standards:
short: at least 16 bitsint: at least as long asshortlong: at least 32 bits, and at least as long asintlong long: at least 64 bits, and at least as long aslong
On systems with 8-bit bytes, 1 byte = 8 bits.
Many systems use minimum lengths: short is 16 bits (2 bytes), long is 32 bits (4 bytes), long long is 64 bits (8 bytes). int is often 4 bytes (same as long).
Use the sizeof operator to check data type sizes, e.g., sizeof(int).
The <climits> header defines symbolic constants like INT_MAX and INT_MIN.
Unsigned Types
Unsigned types store non-negative integers, which increases the maximum value without changing the data size.
int is typically chosen as the natural type for efficiency.
Explicit Type Conversions
Keywords: static_cast, dynamic_cast, reinterpret_cast, const_cast
static_cast
- No runtime type checking for safety.
- Safe for upcasting (derived to base).
- Unsafe for downcasting (base to derived) due to lack of dynamic type checking.
- Used for basic type conversions (e.g.,
inttochar) and converting any type tovoid.
dynamic_cast
- Provides type checking for downcasting (relies on virtual functions), safer than
static_cast. - The target must be a pointer, reference, or
void*; the base class must have virtual functions. - For pointers, failure returns
nullptr; for references, failure throws an exception.
reinterpret_cast
- Converts between integer types and pointers, or between different pointer types. Platform-dependent and risky.
const_cast
- Removes
constorvolatilequalifiers. Converts constant pointers/references to non-constant ones, still pointing to the original object.
Comparing Floating-Point Numbers
Direct equality (==) is unreliable due to representation imprecision.
Compare the absolute difference against a predefined epsilon threshold.
Comparing with zero requires the same caution.
1.3 Keywords
const
Purpose of const
The const keyword specifies immutability for variables, pointers, references, and member functions.
- Constant Variables: Declares constants that must be initialized and cannot be modified later.
- Member Variables: Must be initialized via constructor initializer list; cannot be initialized inside the class definition.
- Member Functions: Declares that the function does not modify object state (for non-
mutablemembers).constobjects cannot call non-constmember functions. - Constant Objects: The object's members cannot be modified.
constParameters: Can accept bothconstand non-constarguments, indicating the function won't modify the passed argument.
Pointer to Constant (Low-level const)
A pointer to a read-only object. The value of the object cannot be modified through this pointer.
Syntax: const type *ptr = &var; or type const *ptr = &var;
int val = 10;
const int* ptr_a = &val;
// *ptr_a = 9; // Error: read-only location
val = 9; // OK
Constant Pointer (Top-level const)
A pointer whose address cannot be changed after initialization.
Syntax: type * const ptr = &var;
int val1 = 10;
int val2 = 12;
int* const ptr_b = &val1;
// ptr_b = &val2; // Error: cannot change pointer target
*ptr_b = 9; // OK
Rule of Thumb: const left of * means pointer to constant; const right of * means constant pointer.
Use volatile to modify a const variable's value. Use mutable on data members that need modification inside const member functions.
static
The static keyword controls variable/function lifetime, scope, and access.
- Static Local Variables: Defined inside a function with
static. Lifetime is the entire program; initialized only once.
void counterFunc() {
static int cnt = 0;
cnt++;
std::cout << "Count: " << cnt << std::endl;
}
- Static Member Variables: Shared across all objects of the class. Must be defined outside the class.
class Example {
public:
static int sharedVar;
};
int Example::sharedVar = 0;
- Static Member Functions: Belong to the class, not an object. Cannot access non-static members directly.
class Example {
public:
static void helper() {
std::cout << "Static method" << std::endl;
}
};
const vs. static
const ensures immutability, while static affects lifetime and sharing.
#define vs. typedef
#define is simple text substitution with no type checking, processed during preprocessing.
typedef creates an alias with type checking, processed during compilation.
#define vs. inline
#define performs text substitution without type safety. inline functions are compiled, type-checked, and may reduce function call overhead.
#define vs. const
const defines a typed constant at compile time, stored in memory. #define is an untyped macro, substituted at preprocessing.
constexpr
constexpr indicates a compile-time constant. It implies const, but not all const variables are constexpr. constexpr functions can be evaluated at compile time.
constexpr int maxItems = 100;
constexpr int total = maxItems + 50;
volatile
Informs the compiler that a variable may change unexpectedly (e.g., by hardware or another thread). Every access reads from memory directly, disabling certain optimizations.
mutable
Allows a data member to be modified inside a const member function.
class Entity {
int id_;
mutable int accessCount_;
public:
void display() const {
// id_ = 10; // Error
accessCount_ = 20; // OK
}
};
explicit
Prevents implicit conversions for constructors (usually single-argument constructors).
extern
Declares a global variable defined in another translation unit. extern "C" enables C linkage for interoperability.
Prefix vs. Postfix Increment/Decrement
struct Iterator {
Iterator& operator++() { // Prefix
++ptr_;
return *this;
}
const Iterator operator++(int) { // Postfix
Iterator tmp = *this;
++*this;
return tmp;
}
private:
int* ptr_;
};
Postfix returns by value (old state), prefix returns by reference (new state). Prefix is often more efficient.
std::atomic
Operations like counter++ or x = y are not inherently thread-safe. std::atomic provides atomic operations on integral types, avoiding data races.
std::atomic<int> safeCounter;
safeCounter = 99;
1.4 struct vs. class
structmembers default to public;classmembers default to private.structinheritance defaults to public;classinheritance defaults to private.- Use
structfor simple data aggregates andclassfor encapsulated objects.
1.5 Storage Class: Static Local, Global, and Local Variables
- Static Local: Scope limited to function, lifetime equals program duration, value persists between calls.
- Global: Scope is the entire program, lifetime equals program duration.
- Local: Scope limited to enclosing block, lifetime ends when the block exits.
1.6 Code Before and After main()
Before main: Stack pointer setup, initialization of static and global variables (.data, .bss), global object constructors, passing argc/argv.
After main: Global object destructors, functions registered with atexit().
1.7 Exception Handling
Use try, throw, and catch to manage runtime errors like division by zero or out-of-bounds access.
try {
if (denominator == 0)
throw std::runtime_error("Division by zero");
result = numerator / denominator;
} catch (const std::exception& e) {
std::cerr << "Caught: " << e.what() << std::endl;
}
C++ standard exception hierarchy includes bad_alloc, out_of_range, bad_cast, etc.
1.8 Function Parameters
Formal parameters are placeholders in the function definition. Actual parameters are the values passed during the call.
Passing Mechanisms:
- By Value: Copies data; safe but potentially expensive.
- By Pointer: Copies address (4/8 bytes); allows modifying original data.
- By Reference: Passes alias; efficient and allows modification. Preferred for large objects.
1.9 Compilation Process
- Preprocessing: Handles
#include,#define, conditionals, generates.i/.iifiles. - Compilation: Translates to assembly (
.sfiles). - Assembly: Converts to machine code object files (
.o/.obj). - Linking: Combines object files and libraries into an executable.
1.10 Static vs. Dynamic Linking
Static Linking: Library code is copied into the executable at compile time.
Dynamic Linking: Shared libraries (.so/.dll) are loaded at runtime, saving disk space and memory through sharing.
1.11 How "hello world" Reaches the Screen
The operating system loads the executable, creates a process, allocates memory, handles page faults, executes the code, makes system calls to display drivers, and finally the monitor displays the output.
1.12 Core Dumps
A core dump is a file recording a process's memory state when it crashes. It can be analyzed with a debugger like GDB to diagnose the failure.
2. C++ Memory Management
2.1 Memory Layout
- Stack: Local variables, function call management. Automatic allocation.
- Heap: Dynamically allocated memory (
new/malloc). Manually managed. - Global/Static Area: Global and static variables.
- Constant Area: Read-only constants like string literals.
- Code Area: Executable code.
Heap vs. Stack
| Feature | Heap | Stack |
|---|---|---|
| Management | Manual by programmer | Automatic by compiler |
| Allocation | Uses free list, potential fragmentation | Continuous space, simple pointer update |
| Size Limit | Limited by virtual memory | Predefined size (e.g., 2MB) |
| Growth Direction | Grows towards higher addresses | Grows towards lower addresses |
| Efficiency | Slower due to complex management | Faster due to hardware support |
2.2 Memory Leaks
A memory leak occurs when dynamically allocated memory is no longer reachable and not freed. Smart pointers (std::unique_ptr, std::shared_ptr) help prevent leaks. Destructors in base classes should be virtual when deleting derived objects through base pointers.
2.3 Smart Pointers
std::unique_ptr: Exclusive ownership. Movable, not copyable.std::shared_ptr: Shared ownership with reference counting.std::weak_ptr: Non-owning observer to breakshared_ptrcycles.
#include <memory>
auto uniquePtr = std::make_unique<Widget>(42);
auto sharedPtr1 = std::make_shared<Widget>(42);
std::shared_ptr<Widget> sharedPtr2 = sharedPtr1;
std::weak_ptr<Widget> weakPtr = sharedPtr1;
2.4 new/delete vs. malloc/free
newthrowsstd::bad_allocon failure;mallocreturnsNULL.newautomatically calculates size;mallocrequires explicit size.newcalls constructors;mallocdoes not.new/deleteare operators;malloc/freeare functions.
2.5 Dangling Pointers
Pointers that retain the address of freed memory. Best mitigated by setting pointers to nullptr after deletion and using smart pointers.
2.6 Memory Alignment
Data alignment ensures variables start at addresses that are multiples of their size, improving CPU memory access efficiency. Structures may include padding to satisfy alignment requirements for all members.
alignas can specify alignment; alignof queries it.
2.7 Out-of-Order Execution
Modern CPUs may reorder instructions for performance. C++ memory model and std::atomic provide barriers to enforce ordering in multi-threaded code.
2.8 Common String Operations
Standard functions like strcpy, strlen, strcat, strcmp can be manually implemented for educational purposes and understanding memory overlap concerns.
char* customCopy(char* destination, const char* source) {
assert(destination && source);
char* start = destination;
while ((*destination++ = *source++) != '\0');
return start;
}
2.9 Declaration vs. Definition
A declaration introduces a name and type, while a definition allocates storage. Multiple declarations are allowed, but only one definition exists for each entity.
2.10 Zero-Copy
Zero-copy techniques reduce CPU involvement in data transfers. std::vector::emplace_back constructs elements in-place, avoiding unnecessary copies or moves.
2.11 Function Call Stack Process
When a function is called, parameters are pushed onto the stack right-to-left, followed by the return address. Local variables are then allocated on the stack.
2.12 Using memset(this, 0, sizeof(*this)) in Constructors
This approach is dangerous if the class contains virtual functions (destroys the vtable pointer) or C++ objects with dynamic resources (corrupts their state).
3. Object-Oriented Programming in C++
3.1 Three Pillars of OOP
- Inheritance: Deriving new classes from existing ones, reusing and extending functionality.
- Encapsulation: Bundling data and methods, restricting access via
public,protected,private. - Polymorphism: Calling derived class methods through base class interfaces. Achieved via function overloading (compile-time) and virtual functions (runtime).
3.2 Access Specifiers and Inheritance
public: Accessible anywhere.protected: Accessible within the class and its derived classes.private: Accessible only within the class itself.
Inheritance mode (public, protected, private) can further restrict base member access in the derived class.
3.3 Multiple Inheritance and Virtual Inheritance
Multiple inheritance allows a class to derive from more than one base class. The Diamond Problem occurs when a class indirectly inherits the same base twice; virtual inheritance solves this by ensuring only one shared base sub-object.
class Base { public: int data; };
class DerivedA : virtual public Base {};
class DerivedB : virtual public Base {};
class Final : public DerivedA, public DerivedB {};
3.4 Overloading vs. Overriding
- Overloading: Same function name, different parameter lists, resolved at compile time.
- Overriding: Redefining a virtual function in a derived class, resolved at runtime. Function signature must match.
3.5 How Polymorphism Works
Polymorphism relies on virtual function tables (vtables). Each polymorphic class has a vtable containing pointers to its virtual functions; each object has a vptr pointing to the correct vtable.
3.6 Static vs. Non-static Members
Non-static members belong to objects; static members belong to the class. Static member functions cannot access non-static members directly as they lack a this pointer.
3.7 Constructors and Destructors
- Constructors: Initialize objects. Types include default, parameterized, copy, move, and delegating.
- Destructors: Clean up resources. Base class destructors should be virtual when inheritance and polymorphism are used.
3.8 Virtual Functions
- Virtual Functions: Allow derived classes to provide specific implementations. Use the
virtualkeyword. - Pure Virtual Functions:
virtual void func() = 0;Makes the class abstract and non-instantiable. - Virtual Destructors: Ensure derived destructors are called when deleting objects through base pointers.
Constructors cannot be virtual; virtual functions should generally not be called within constructors or destructors as the dynamic binding may not behave as expected.
3.9 Deep Copy vs. Shallow Copy
- Deep Copy: Duplicates not only the object but also dynamically allocated resources.
- Shallow Copy: Copies member values only, potentially leading to two objects sharing the same resource.
3.10 Operator Overloading
Operators can be overloaded as member or non-member functions. Follow established conventions for symmetry and clarity. Lambdas are essentially objects with an overloaded operator().
3.11 Initialization Lists
Using the constructor initializer list is more efficient than assignment within the constructor body, and it is required for initializing references, const members, and base classes that lack default constructors.
3.12 Static vs. Dynamic Types and Binding
- Static Type: The type known at compile time.
- Dynamic Type: The actual derived type at runtime.
- Static Binding: Resolved at compile time (non-virtual functions).
- Dynamic Binding: Resolved at runtime via virtual functions.
3.13 Inheritance vs. Composition
- Inheritance (is-a): Causes tight coupling; changes to base can affect derived classes.
- Composition (has-a): Promotes loose coupling; objects can be replaced dynamically.
3.14 Default Functions in an Empty Class
An empty class in C++ will have a compiler-generated default constructor, copy constructor, destructor, and copy assignment operator.
3.15 The this Pointer
this is an implicit pointer to the current object inside non-static member functions. It points to the object's starting address, enabling access to member variables.
3.16 Calling delete this
Dangerous. It destroys the object while it might still be in use. If called directly in a destructor, it causes infinite recursion and a stack overflow.
3.17 Object Size Calculation
- Empty class: 1 byte (to ensure unique addresses).
- Class with virtual functions: includes a vptr (4 or 8 bytes).
- Static members: stored outside the object, do not affect size.
4. C++ Standard Template Library (STL)
4.1 STL Overview
STL consists of six components: Containers, Algorithms, Iterators, Functors, Adapters, and Allocators.
4.2 Core Containers
- Sequence:
vector,list,deque,array,forward_list. - Associative:
set,multiset,map,multimap, unordered variants. - Adapters:
stack,queue,priority_queue.
std::vector Internals
A dynamic array that grows by reallocating a larger block when capacity is exceeded. Common strategies include doubling the capacity. Operations like resize change the number of elements, while reserve changes the capacity.
std::list
A doubly-linked list. Insertions and deletions are O(1) anywhere, but random access is not supported.
| Feature | std::vector |
std::list |
|---|---|---|
| Access | Fast random access O(1) | No random access (sequential) |
| Insert/Delete | O(1) at end, O(n) elsewhere | O(1) everywhere |
| Memory | Contiguous block, may overallocate | Non-contiguous nodes |
std::deque
Double-ended queue composed of multiple memory chunks, supporting fast insertions at both ends and random access.
std::map vs. std::unordered_map
map: Implemented as a red-black tree, ordered keys, O(log n) operations.unordered_map: Hash table, average O(1) operations, unordered keys.
push_back vs. emplace_back
emplace_back constructs an element in-place using forwarded arguments, avoiding a temporary object creation.
4.3 Iterators
Iterators abstract the traversal of container elements. Erasing elements can invalidate iterators depending on the container type (e.g., vector iterators are invalidated).
4.4 std::string and const char* Conversions
std::string s = "abc";
const char* c_str = s.c_str(); // string to const char*
const char* literal = "abc";
std::string s2(literal); // literal to string
4.5 Traits Technique
Traits provide compile-time type information. iterator_traits deduces associated types like value_type; type_traits inspects class properties like trivial constructors/destructors to optimize algorithms.
4.6 Trivial Destructors
A destructor automatically generated by the compiler. STL allocators can optimize by skipping destructor calls for trivially destructible types.
4.7 RAII (Resource Acquisition Is Initialization)
Resources are acquired in a constructor and released in the destructor, tying resource lifecycle to object scope. Smart pointers are a prime example.
4.8 Two-Level Allocator
STL uses a two-tier allocator. For blocks larger than 128 bytes, malloc/free are used directly. For smaller blocks, a memory pool of fixed-size free lists (multiples of 8 bytes, up to 128) manages allocations to reduce fragmentation and overhead.
4.9 Hash Table Collision Resolution
Common strategies include chaining (separate lists) and open addressing (linear, quadratic, double hashing).
5. C++ Templates and Generic Programming
5.1 Full and Partial Specialization
Templates can be specialized for specific types to provide more efficient implementations. Function templates support only full specialization; class templates support both full and partial specialization.
5.2 Template Implementation
The compiler generates a concrete function or class from a template when instantiated. The template definition must be visible in the translation unit where it is used.
6. Modern C++ Features
6.1 C++11 Highlights
- Type Deduction:
autoanddecltype. - Smart Pointers:
unique_ptr,shared_ptr,weak_ptr. - Move Semantics: Rvalue references
Type&&andstd::movefor efficient resource transfer. - Perfect Forwarding:
std::forward. - Lambda Expressions: Inline anonymous functions.
- Uniform Initialization: Braced initializer lists.
- Null Pointer:
nullptrto replaceNULL. - Range-Based
forLoops.
6.2 Smart Pointers in Detail
shared_ptr: maintains a reference count for shared ownership.unique_ptr: exclusive ownership.weak_ptr: observers ashared_ptrwithout affecting its reference count, breaking cycles.
6.3 Type Deduction: auto and decltype
auto deduces types like template argument deduction, stripping references and cv-qualifiers. decltype yields the exact type of an expression, preserving references and cv-qualifiers.
6.4 Rvalue References and Move Semantics
An rvalue reference (T&&) binds to temporary objects. std::move casts an lvalue to an rvalue, enabling the transfer of resources via move constructors.
std::string source = "temporary";
std::string dest = std::move(source); // source is now in a valid but unspecified state
6.5 nullptr
nullptr is a distinct type (std::nullptr_t) that avoids ambiguity with integers, solving a long-standing overloading problem.
6.6 Range-based for Loop
Simplifies container iteration:
std::vector<int> values = {1, 2, 3};
for (auto& elem : values) {
std::cout << elem << std::endl;
}
6.7 Uniform Initialization
Braced initialization {} prevents narrowing conversions and provides a consistent syntax.
6.8 Lambda Expressions
[captures](parameters) -> return_type { body }. Captures allow the lambda to access surrounding variables by value or reference.
6.9 Concurrency
std::thread: creates a thread. Ensure thread object outlives the execution ordetach()it.std::mutexwithstd::lock_guard(simple, scoped locking) andstd::unique_lock(deferred locking, movable, used with condition variables).- Condition variables synchronize threads based on logical conditions.
- Read-Write locks allow concurrent reads but exclusive writes.