RoadMap notes Flashcards
Functions, Data Types, Pointers and ref, structuring codebase, structure and classes
Static Typing
the data type of a variable is determined at compile time, before the program is executed. This means that a variable can be used only with data of a specific type, and the compiler ensures that the operations performed with the variable are compatible with its type.
C++ is a statically typed language, which means that it uses static typing to determine data types and perform type checking during compile time. This helps with ensuring type safety and can prevent certain types of errors from occurring during the execution of the program.
int num = 42;
// ‘num’ is statically typed as an integer
Dynamic Typing in C++
C++ is known as a statically-typed language, which means the data types of its variables are determined at compile time. However, C++ also provides concepts to have certain level of dynamic typing, which means determining the data types of variables at runtime.
void* pointer
generic pointer that can point to objects of any data type. They can be used to store a reference to any type of object without knowing the specific type of the object.
int x = 42;
void_ptr = &x;
std::cout «_space;(static_cast<int>(void_ptr)) «_space;‘\n’;
std::any (C++ 17)
class which represents a generalized type-safe container for single values of any type.
std::any any_value;
any_value = 42; std::cout << std::any_cast<int>(any_value) << '\n';
Lambda function
A lambda function, or simply “lambda”, is an anonymous (unnamed) function that is defined in place, within your source code, and with a concise syntax. Lambda functions were introduced in C++11 and have since become a widely used feature, especially in combination with the Standard Library algorithms.
Lambda function syntax
capture-list -> return_type {
// function body
};
capture-list: A list of variables from the surrounding scope that the lambda function can access.
parameters: The list of input parameters, just like in a regular function. Optional.
return_type: The type of the value that the lambda function will return. This part is optional, and the compiler can deduce it in many cases.
function body: The code that defines the operation of the lambda function.
Lambda function, no capture
Lambda function with parameters
Lambda function with capture-by-value
int multiplier = 3;
auto times = multiplier {
return a * multiplier;
};
int result = times(5); // result = 15
Lambda function with capture-by-reference
int expiresInDays = 45;
auto updateDays = &expiresInDays {
expiresInDays = newDays;
};
updateDays(30); // expiresInDays = 30
Reference
considered as a constant pointer (not to be confused with a pointer to a constant value) which always points to (references) the same object. They are declared using the & (ampersand) symbol.
int var = 10;
// Declare an integer variable
int& ref = var;
// Declare a reference that “points to” var
You can use references as function parameters to create an alias for an argument. This is commonly done when you need to modify the original variable or when passing an object of considerable size to avoid the cost of copying.
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
Smart pointer: unique_ptr
unique_ptr works on the concept of exclusive ownership - meaning only one unique_ptr is allowed to own an object at a time. This ownership can be transferred or moved, but it cannot be shared or copied.
This concept helps to prevent issues like dangling pointers, reduce memory leaks, and eliminates the need for manual memory management. When the unique_ptr goes out of scope, it automatically deletes the object it owns.
std::unique_ptr<int> p1(new int(5));
// Initialize with pointer to a new integer</int>
std::unique_ptr<int> p2 = std::make_unique<int>(10);
// Preferred method (C++14 onwards)</int></int>
Transferring ownership from pointer
std::unique_ptr<int> p1(new int(5));</int>
std::unique_ptr<int> p2 = std::move(p1); // Ownership is transferred from p1 to p2
unique_ptr with custom deleters
struct MyDeleter {
void operator()(int* ptr) {
std::cout «_space;“Custom Deleter: Deleting pointer” «_space;std::endl;
delete ptr;
}
};
std::unique_ptr<int, MyDeleter> p1(new int(5), MyDeleter());
Remember that since unique_ptr has exclusive ownership, you cannot use it when you need shared access to an object.
For such cases, you can use std::shared_ptr.
shared_ptr
type of smart pointer in C++ that allows multiple pointers to share ownership of a dynamically allocated object. The object will be automatically deallocated only when the last shared_ptr that points to it is destroyed.
When using a shared_ptr, the reference counter is automatically incremented every time a new pointer is created, and decremented when each pointer goes out of scope. Once the reference counter reaches zero, the system will clean up the memory.
class MyClass {
public:
MyClass() { std::cout «_space;“Constructor is called.” «_space;std::endl; }
~MyClass() { std::cout «_space;“Destructor is called.” «_space;std::endl; }
};
// create a shared pointer to manage the MyClass object
std::shared_ptr<MyClass> ptr1(new MyClass());</MyClass>
{ // create another shared pointer and initialize it with the previously created pointer std::shared_ptr<MyClass> ptr2 = ptr1; }
In this example, ptr1 and ptr2 share ownership of the same object. The object is only destroyed when both pointers go out of scope and the reference counter becomes zero.
weak_ptr
type of smart pointer in C++ that adds a level of indirection and safety to a raw pointer. It is mainly used to break reference cycles in cases where two objects have shared pointers to each other, or when you need a non-owning reference to an object that is managed by a shared_ptr.
A weak_ptr doesn’t increase the reference count of the object it points to, which is a crucial distinction between weak_ptr and shared_ptr. This ensures that the object will be deleted once the last shared_ptr that owns it goes out of scope, even if there are weak_ptrs still referencing it.
To use a weak_ptr, you must convert it to a shared_ptr using the lock() function, which tries to create a new shared_ptr that shares ownership of the object. If successful, the object’s reference count is increased and you can use the returned shared_ptr to safely access the object.
Here’s an example of using weak_ptr:
std::weak_ptr<MyClass> weak;</MyClass>
{ std::shared_ptr<MyClass> shared = std::make_shared<MyClass>(); weak = shared; if(auto sharedFromWeak = weak.lock()) { sharedFromWeak->DoSomething(); // Safely use the object std::cout << "Shared uses count: " << sharedFromWeak.use_count() << '\n'; // 2 } }
// shared goes out of scope and the MyClass object is destroyed
if(auto sharedFromWeak = weak.lock()) { // This block will not be executed because the object is destroyed } else { ...}
In this example, we create a shared_ptr named shared that manages a MyClass object. By assigning it to a weak_ptr named weak, we store a non-owning reference to the object. Inside the inner scope, we create a new shared_ptr named sharedFromWeak using weak.lock() to safely use the object. After the inner scope, the MyClass object is destroyed since shared goes out of scope, and any further attempt to create a shared_ptr from weak will fail as the object is already destroyed.
Raw pointers in C++ are low-level constructs that directly hold a memory address.
They can be used for manually allocating memory, creating dynamic arrays, and passing values efficiently, among other things.
new operator
used to allocate memory on the heap. The memory allocated using new remains available until you explicitly deallocate it using the corresponding delete operator.
int* ptr = new int;
// Dynamically allocates an int on the heap
*ptr = 42;
// Assigns the value 42 to the allocated int