C++ Notes
Updated: 07 Jan 2025
C++ Fundamentals
Here, I follow the mental framework I discuss in fundamentals of programming . First is the familiazation of the different “objects” in C++. These are the main things you work with, the container of the information or data you want to work on. Then, a mastery of the function or mapping technique to work on these “objects”.
An important concern is how data flow in the function. This is central to developing a deeper understanding of C++. I will delve into this more.
The fundamental paradigms in C++: procedural, object-oriented, and generic programming.
Core Principles
The foundation of C++ rests on these key ideas:
- Procedural Programming: This involves writing a sequence of steps or procedures (functions) to be executed by the computer to accomplish a task.
- Object-Oriented Programming (OOP): This paradigm organizes code into “objects,” which bundle data and the functions that operate on that data. The core concepts of OOP are encapsulation, inheritance, and polymorphism.
- Generic Programming: This focuses on writing algorithms and data structures that can work with a variety of data types, promoting code reusability.
A crucial aspect of C++ is its static typing, where the data type of a variable is known at compile time. This allows the compiler to catch errors early. Additionally, C++ provides developers with direct memory management capabilities, offering fine-grained control over how memory is used.
“Objects” in C++
- fundamental data types:
int,double,char,bool - compound types:
- arrays: collections of elements of the same type stored in contiguous memory locations.
- modern c++ object:
- array:
std::array - vector:
std::vector
- array:
- modern c++ object:
- pointers: variables that store the memory address of another object.
- references: an alias or another name for an existing object
- arrays: collections of elements of the same type stored in contiguous memory locations.
- user-defined types: classes
- a
classis a blueprint for creating objects. - It defines a set of attributes (data members) and behaviors (member functions).
- An object is an instance of a class.
- a
Mappings/Functions
In C++, a function is a named block of code that performs a specific task. Functions are the primary mechanism for procedural programming and are essential for organizing and reusing code.
int add(int a, int b) {
return a + b;
}Interrelationship of Objects and Functions
The fundamental components of C++—objects and functions—are deeply intertwined. Their relationship forms the basis of how a C++ program is structured and operates.
- Functions Operate on Objects: The primary role of a function is to manipulate data. This data is stored in objects (which can be instances of fundamental types or classes). Functions take objects as parameters, process them, and can return objects as results.
- Objects Contain Functions: In object-oriented programming, classes encapsulate both data (attributes) and the functions that operate on that data. These functions, called member functions or methods, define the behavior of the objects of that class. For instance, a
Carobject might have member functions likestartEngine()andaccelerate(). - Data Flow: Variables (which are simple objects) are defined and then passed to functions. These functions, in turn, can modify the state of these variables or create new ones. This flow of data through functions is central to program execution.
Data Flow
When data flows in C++, the primary techniques involve how you pass data into and out of functions.
Passing Data to Functions: there are three main ways to pass data into a function:
- pass-by-value: copies the value of an argument into the function’s parameter.
- Any changes made to the parameter inside the function do not affect the original argument.
- pass-by-reference (
&): passes an alias (or reference) to the original argument, not a copy.- Any modifications made to the parameter inside the function will affect the original argument.
- It’s highly efficient as it avoids making copies, making it ideal for large objects or when you intend to modify the original data.
- Use when: You want to avoid a costly copy or when you need the function to change the original variable’s value.
void modifyReference(int& x) {
x = 100; // The original variable is changed
}
int main() {
int original = 10;
modifyReference(original);
// original is now 100
return 0;
}- for vectors, an efficient way to handle compound objects is by passing a reference, which is an alias for the original object.
// The '&' makes it pass-by-reference.
void addElement(std::vector<int>& vec) {
// This modifies the ORIGINAL vector.
vec.push_back(100);
}
// const& provides efficient, safe, read-only access.
void printVector(const std::vector<int>& vec) {
// for (int num : vec) { ... }
// vec.push_back(100); // This would cause a COMPILE ERROR.
}- pass-by-pointer (
*): involves passing the memory address of an argument to the function.- Use when: You need to modify the original data or when you want to represent “no object” by passing a
nullptr.
- Use when: You need to modify the original data or when you want to represent “no object” by passing a
void modifyPointer(int* ptr) {
if (ptr != nullptr) {
*ptr = 100; // The data at the memory address is changed
}
}
int main() {
int original = 10;
modifyPointer(&original);
// original is now 100
return 0;
}- a C-style array has unique behavior when passed to a function: it acts as a pointer. You aren’t passing the array itself, only the memory address of its first element
- what it means: The function receives a pointer to the original array. There is no copy made.
- consequence: Any modification the function makes to the array’s elements will change the original array.
- limitation: Because the function only gets a pointer, it doesn’t know the array’s size. You must pass the size as a separate argument.
// The function receives a pointer to the first element and the size.
void printArray(int arr[], int size) {
for (int i = 0; i < size; ++i) {
// This modifies the ORIGINAL array.
arr[i] = arr[i] * 2;
}
}
int main() {
int myNumbers[] = {1, 2, 3, 4};
int size = 4;
printArray(myNumbers, size); // Pass the array
// myNumbers is now {2, 4, 6, 8}
return 0;
}Getting Data from Functions: the most common way to get data back from a function is through its return value.
- Return by Value: The function returns a copy of a value to the caller. This is suitable for simple data types.
- Return by Reference or Pointer: For larger objects, returning by reference or pointer can be more efficient as it avoids copying. However, you must ensure the object being referred to does not go out of scope when the function ends.
- Output parameters: This involves passing an argument by reference or pointer, not for its input value, but so the function can place its result into it.
Data Flow in Objects: in object-oriented C++, data flow is often managed within an object itself.
- Member Functions: A class’s member functions (methods) can directly access and modify the object’s private data members. This encapsulates the data, meaning it can only be changed in controlled ways, which is a core principle of object-oriented design.
class Account {
private:
double balance;
public:
void deposit(double amount) {
if (amount > 0) {
balance += amount; // Internal data flow: method modifies the object's state
}
}
double getBalance() const {
return balance; // Data flows out of the object
}
};Object-Oriented Programming
The fundamentals of C++ object-oriented programming (OOP) are four core principles that help structure code around data and behavior: encapsulation, abstraction, inheritance, and polymorphism.
Encapsulation
Encapsulation is the bundling of data (attributes) and the methods (functions) that operate on that data into a single unit, called a class. It also involves restricting direct access to an object’s internal state, which is known as data hiding.
- Purpose: To protect an object’s internal data from unintended outside modification. This is achieved using access specifiers like private and public.
- Analogy: Think of a car. You interact with it through a simple interface (steering wheel, pedals), but the complex engine mechanics (private data) are hidden and protected under the hood.
class Account {
private:
double balance; // Data is hidden from the outside world
public:
// Public methods provide controlled access
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
};Abstraction
Abstraction means hiding complex implementation details and showing only the essential features of the object. It’s the result of encapsulation. The goal is to simplify a complex system by modeling classes appropriate to the problem.
- Purpose: To reduce complexity by hiding unnecessary details from the user. You focus on what an object does, not how it does it.
- Analogy: The car’s dashboard is an abstraction. It shows you essential information like speed and fuel level without exposing the complex sensor and calculation details behind them.
Inheritance
Inheritance is a mechanism that allows a new class (the derived or child class) to acquire the properties and behaviors of an existing class (the base or parent class).
- Purpose: To promote code reuse and establish a clear “is-a” relationship between classes. For example, a Car is a type of Vehicle.
- Analogy: You inherit traits like eye color from your parents. You are a “child” class with your own unique attributes, but you also possess the attributes of your “parent” class.
// Base class
class Vehicle {
public:
void startEngine() { /* ... */ }
};
// Derived class inherits from Vehicle
class Car : public Vehicle {
public:
void drive() { /* ... */ }
};Polymorphism
Polymorphism, which means “many forms,” allows objects of different classes to be treated as objects of a common base class. The most common form in C++ is runtime polymorphism, achieved through virtual functions.
- Purpose: To write flexible and reusable code that can work with objects of different types without needing to know their specific class.
- Analogy: You have a “start” button for multiple devices (a car, a laptop, a game console). The button is the common interface, but the “start” action it triggers is different for each device.
class Animal {
public:
virtual void makeSound() { /* ... */ } // Virtual function
};
class Dog : public Animal {
public:
void makeSound() override { /* Barks */ }
};
class Cat : public Animal {
public:
void makeSound() override { /* Meows */ }
};Other important details …
The constructor’s purpose is to initialize an object when it’s created, while the destructor’s purpose is to clean up resources when the object is destroyed. They work together to manage an object’s lifecycle automatically.
Constructor
A constructor is a special member function that is automatically called when an object of a class is created. Its primary job is to ensure the object is in a valid and usable state from the moment it exists.
- Initialization: It sets the initial values of an object’s data members.
- Resource Acquisition: It can acquire any resources the object needs to function, such as dynamic memory, file handles, or network connections.
PairVashishta::PairVashishta(LAMMPS *lmp) : Pair(lmp)
{
single_enable = 0;
restartinfo = 0;
one_coeff = 1;
manybody_flag = 1;
centroidstressflag = CENTROID_NOTAVAIL;
unit_convert_flag = utils::get_supported_conversions(utils::ENERGY);
params = nullptr;
r0max = 0.0;
maxshort = 10;
neighshort = nullptr;
}Destructor
A destructor is a special member function, identified by a tilde (~) before the class name, that is automatically called just before an object is destroyed. Its purpose is to perform cleanup tasks.
- Resource Release: It frees up any resources the object acquired during its lifetime. This is crucial for preventing resource leaks (like memory leaks).
- Finalization: It can perform any final actions before the object ceases to exist.
PairVashishta::~PairVashishta()
{
if (copymode) return;
memory->destroy(params);
memory->destroy(elem3param);
if (allocated) {
memory->destroy(setflag);
memory->destroy(cutsq);
memory->destroy(neighshort);
}
}This automatic calling of the constructor and destructor is a cornerstone of a powerful C++ concept called RAII (Resource Acquisition Is Initialization), which ensures that resources are properly managed throughout an object’s life.
Member access operator
Both . and -> are member access operators used to access the attributes and methods of a class object. The key difference is what you’re using to access them.
Dot operator (.):
Use the dot operator when you are working directly with an object.
#include <string>
class Person {
public:
std::string name;
};
int main() {
Person p1; // p1 is the actual object
p1.name = "Alice"; // Use '.' to access the 'name' member of the object
return 0;
}Arrow operator (->):
Use the arrow operator when you are working with a pointer to an object. It’s a convenient shorthand that dereferences the pointer (*) and then accesses the member (.).
#include <string>
class Person {
public:
std::string name;
};
int main() {
Person* p2 = new Person(); // p2 is a pointer to a Person object
p2->name = "Bob"; // Use '->' to access 'name' through the pointer
delete p2;
return 0;
}Generic Programming
Generic programming in C++ is built on the interplay of four key concepts, primarily found in the Standard Template Library (STL).
Basic Concepts
- Templates (The “How”):
Templates are the C++ language feature that makes generic programming possible. They act as blueprints that the compiler uses to generate code for specific types at compile time.
- template is a meta-feature that works on top of functions and classes to make them generic.
- Connection to Functions: A function template is a direct blueprint for a function. It defines the logic of a function without tying it to specific data types. The compiler uses this blueprint to generate the concrete functions you need.
- Connection to Objects: A class template is a direct blueprint for a class. You use this blueprint to define a type (e.g., Box
), from which you then create objects (e.g., intBox). - The Interrelationship: The true power of templates is how they weave functions and objects together in a generic way.
- You can write a function template that takes objects created from a class template as its arguments.
- A class template can have member functions that are themselves generic and operate on the template type T. This creates a powerful system where you can define generic algorithms (function templates) that operate on generic data structures (objects from class templates). Function Templates: Blueprints for functions. For example, a single max() function template can work with integers, doubles, or any type that supports the > operator.
Class Templates are blueprints for classes. This is how generic containers like
std::vectorare created. You can have astd::vector<int>or astd::vector<std::string>from the same template. - template is a meta-feature that works on top of functions and classes to make them generic.
#include <iostream>
#include <string>
// Template for a generic Box class
template <typename T>
class Box {
private:
T value;
public:
void set(T val) {
value = val;
}
T get() {
return value;
}
};
int main() {
// Create an object from the class template to hold an int
Box<int> intBox;
intBox.set(123);
// Create another object to hold a string
Box<std::string> stringBox;
stringBox.set("Hello");
std::cout << intBox.get() << std::endl; // Outputs 123
std::cout << stringBox.get() << std::endl; // Outputs "Hello"
return 0;
}
- Containers (The “What”):
Containers are class templates that manage a collection of objects. They are the data structures you use to store your data.
- Purpose: To provide efficient and flexible ways to store data.
- Examples:
std::vector(dynamic array)std::list(doubly-linked list)std::map(key-value pairs)
- Iterators (The “Glue”):
Iterators are objects that act like generalized pointers, providing a uniform way to access elements in a container, regardless of how the container is structured internally.
- Purpose: To connect algorithms with containers. An algorithm doesn’t need to know if it’s working on a vector or a list; it just needs a pair of iterators that define a sequence of elements.
- Key Operations:
begin()(get an iterator to the first element),end()(get an iterator to past the last element),++(move to the next element),*(access the element).
- Algorithms (The “Do”):
Algorithms are standalone template functions that perform common operations on sequences of elements, such as sorting, searching, and transforming.
- Purpose: To provide reusable, efficient, and proven implementations of common tasks.
- Key Feature: They operate on iterators, not directly on containers. This makes them incredibly flexible. For example, std::sort can sort any sequence defined by a pair of iterators, whether it’s a full vector or just a small part of it.
- Examples:
std::sortstd::findstd::for_eachstd::copy
Compilation Stages
- preprocessing : the preprocessor scans the source code for directives, which are lines starting with a
#. The most common directive,#include, instructs the preprocessor to insert the content of a specified file into the current file. Another key directive,#define, is used to create macros, which are essentially text replacements. The output of this stage is a single, expanded source code file. - compilation : the compiler takes the preprocessed code and translates it into assembly language, which is a low-level language that is specific to the computer’s architecture. It then assembles this code into an object file, which contains machine code but is not yet a complete executable. These files typically have a
.oor.objextension. - linking : the linker’s job is to take the object file(s) generated by the compiler and combine them with any necessary libraries to produce a final executable file. Libraries are collections of pre-compiled code that provide common functionalities.
g++ hello.cpp -o hello