Advanced C++ Programming: From Fundamentals to Polymorphism
Development Environment and Project Configuration
When managing multiple source files within a single C++ project using CMake, you can automate the generation of executables for each .cpp file found in the source directory. This is particularly useful for competitive programming or educational modules.
# Locate all .cpp files recursively
file(GLOB_RECURSE source_files *.cpp)
# Generate an executable for every detected source file
foreach(src_file ${source_files})
string(REGEX REPLACE ".+/(.+)\\..*" "\\1" bin_name ${src_file})
add_executable(${bin_name} ${src_file})
message(STATUS "Compiling ${src_file} into binary: ${bin_name}")
endforeach()
Data Types and Memory Footprint
Integer and Float Precision
The sizeof operator is essential for determining the memory allocation of different data types in bytes. C++ distinguishes between signed and unsigned integers; unsigned types are restricted to non-negative values, effectively doubling the positive range for the same byte size.
Floating-point precision in C++ typically provides 7 significant digits for float. When outputting large values or precise decimals, using std::fixed and std.precision ensures accuracy in display.
#include <iostream>
#include <iomanip>
int main() {
float pi_approx = 3.1415926535f;
// Set precision and fixed notation
std::cout << std::fixed << std::setprecision(5) << pi_approx << std::endl;
return 0;
}
Character and String Representation
Characters in C++ are mapped to the ASCII table. Internally, a char is an integer. Strings can be managed via C-style character arrays or the more robust std::string class.
C++ Memory Management Model
C++ applications segment memory into four distinct areas to manage data lifecycles effectively:
- Code Area: Stores the binary instructions executed by the CPU. This area is read-only and shared among process instances.
- Global Area: Contains global variables, static variables, and constants. This memory is managed by the OS and released only when the program terminates.
- Stack Area: Managed by the compiler. It stores local variables and function parameters. Memory is automatically allocated and deallocated as functions enter and exit scope.
- Heap Area: Managed by the programmer via
newanddeleteoperators. If memory is not manually freed, the OS reclaims it upon program exit.
int* heap_ptr = new int(100); // Allocate on heap
delete heap_ptr; // Manual release
heap_ptr = nullptr; // Prevent dangling pointer
References and Functions
A reference acts as an alias for an existing varible. Unlike pointers, references must be initialized upon declaration and cannot be redirected to another variable later. Internally, a reference is implemented as a constant pointer (Type * const ptr).
Reference Parameters and Returns
Passing by reference is more efficient than passing by value as it avoids copying large objects. How ever, returning a reference to a local stack variable is a critical error, as that memory is destroyed once the function returns.
void swap_values(int &val1, int &val2) {
int temp = val1;
val1 = val2;
val2 = temp;
}
Object-Oriented Programming: Encapsulation
C++ uses class and struct to encapsulate data and behavior. The primary difference is the default access level: struct defaults to public, while class defaults to private.
- Public: Accessible from anywhere.
- Protected: Accessible within the class and its subclasses.
- Private: Accessible only within the class itself.
Constructor and Destructor Lifecycle
Constructors initialize objects, while destructors perform cleanup. If a class allocates heap memory, you must implement a Deep Copy in the copy constructor to ensure that each object owns a unique memory buffer, preventing double-deletion errors.
class DataContainer {
public:
int* m_buffer;
DataContainer(int size) {
m_buffer = new int(size);
}
// Deep Copy Constructor
DataContainer(const DataContainer& other) {
m_buffer = new int(*other.m_buffer);
}
~DataContainer() {
delete m_buffer;
}
};
This Pointer and Const Members
The this pointer is an implicit parameter in non-static member functions, pointing to the object instance. When a member functon is marked as const, it cannot modify non-mutable member variables.
Operator Overloading
Operator overloading allows custom types to use standard operators like +, <<, and ++. Binary operators are often overloaded as member functions, while operators like << (output stream) must be global to allow the std::ostream object to appear on the left side.
// Overloading prefix increment
MyInt& operator++() {
this->m_val++;
return *this;
}
// Overloading postfix increment using a dummy int parameter
MyInt operator++(int) {
MyInt temp = *this;
this->m_val++;
return temp;
}
Inheritance and Polymorphism
Inheritance allows a subclass to acquire the properties of a base class. C++ supports public, protected, and private inheritance, which determines the access level of inherited members in the derived class.
Polymorphism and Virtual Functions
Polymorphism allows a base class pointer or reference to invoke behavior specific to a derived class at runtime. This is achieved through virtual functions and the Virtual Function Table (V-Table).
class Shape {
public:
virtual void draw() { std::cout << "Drawing Shape" << std::endl; }
virtual ~Shape() {} // Virtual destructor is vital for cleanup
};
class Circle : public Shape {
public:
void draw() override { std::cout << "Drawing Circle" << std::endl; }
};
void render(Shape& s) {
s.draw(); // Dynamic binding
}
Pure Virtual Functions and Abstract Classes
A class becomes abstract if it contains at least one pure virtual function (virtual void func() = 0;). Abstract classes cannot be instantiated and serve as blueprints for derived classes.