RoadMap notes Flashcards

Functions, Data Types, Pointers and ref, structuring codebase, structure and classes

1
Q

Static Typing

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Dynamic Typing in C++

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

void* pointer

A

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 &laquo_space;(static_cast<int>(void_ptr)) &laquo_space;‘\n’;

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

std::any (C++ 17)

A

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';
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Lambda function

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Lambda function syntax

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Lambda function, no capture

A

auto printHello = {
std::cout &laquo_space;“Hello, World!” &laquo_space;std::endl;
};
printHello(); // Output: Hello, World!

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Lambda function with parameters

A

auto add = {
return a + b;
};
int result = add(3, 4); // result = 7

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Lambda function with capture-by-value

A

int multiplier = 3;
auto times = multiplier {
return a * multiplier;
};
int result = times(5); // result = 15

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Lambda function with capture-by-reference

A

int expiresInDays = 45;
auto updateDays = &expiresInDays {
expiresInDays = newDays;
};
updateDays(30); // expiresInDays = 30

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Reference

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

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.

A

void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Smart pointer: unique_ptr

A

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>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Transferring ownership from pointer

A

std::unique_ptr<int> p1(new int(5));</int>

std::unique_ptr<int> p2 = std::move(p1);  // Ownership is transferred from p1 to p2
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

unique_ptr with custom deleters

A

struct MyDeleter {
void operator()(int* ptr) {
std::cout &laquo_space;“Custom Deleter: Deleting pointer” &laquo_space;std::endl;
delete ptr;
}
};

std::unique_ptr<int, MyDeleter> p1(new int(5), MyDeleter());

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Remember that since unique_ptr has exclusive ownership, you cannot use it when you need shared access to an object.

A

For such cases, you can use std::shared_ptr.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

shared_ptr

A

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 &laquo_space;“Constructor is called.” &laquo_space;std::endl; }
~MyClass() { std::cout &laquo_space;“Destructor is called.” &laquo_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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

weak_ptr

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

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:

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Raw pointers in C++ are low-level constructs that directly hold a memory address.

A

They can be used for manually allocating memory, creating dynamic arrays, and passing values efficiently, among other things.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

new operator

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

delete operator

A

used to deallocate memory that has been allocated using new. After memory is deallocated, it’s available to be reallocated for other purposes. Failing to properly deallocate memory can lead to memory leaks.

Here’s an example of using the delete operator:
int* ptr = new int;
// Dynamically allocates an int on the heap
*ptr = 42;
// Assigns the value 42 to the allocated int

delete ptr;
// Deallocates the memory assigned to ptr

23
Q

new[] and delete[] operator

A

used for allocating and deallocating memory for an array of objects. The syntax for new[] and delete[] is very similar to that of new and delete.

Here’s an example of using the new[] and delete[] operators:

int n = 10;
int* arr = new int[n]; // Dynamically allocates an array of 10 integers on the heap

// Set some values in the array
for (int i = 0; i < n; i++) {
arr[i] = i;
}

delete[] arr; // Deallocates the memory assigned to the array

24
Q

raw pointers, and new and delete operators allow manual memory management in C++, providing control over allocation and deallocation.

A

Make sure to always deallocate memory allocated with new or new[], to avoid memory leaks in your programs.

25
Q

Memory leakage

A

Memory leakage occurs when a program allocates memory in the heap but does not release the memory back to the operating system when it is no longer needed. Over time, this leads to exhaustion of available memory, resulting in low system performance or crashes.

In C++, when you use raw pointers, you need to manage the memory allocation and deallocation manually. In many cases, you will use the new keyword to allocate memory for an object in the heap and use delete keyword to deallocate that memory when it’s no longer needed. Forgetting to do this can cause memory leaks.

void create_memory_leak() {
int* ptr = new int[100]; // Allocating memory in the heap for an array of integers
// Some code…
// Code to deallocate the memory is missing: delete[] ptr;
} // ptr goes out of scope, memory block allocated is not deallocated, causing a memory leak.

26
Q

To avoid memory leaks, you should always ensure that memory is deallocated before a pointer goes out of scope or is reassigned.

A

Some ways to achieve this include using the C++ smart pointers (std::unique_ptr, std::shared_ptr), RAII (Resource Acquisition Is Initialization) techniques, and containers from the C++ standard library that manage memory allocation internally (e.g., std::vector, std::string).

27
Q

C++ memory model

A

defines how the program stores and accesses data in computer memory. It consists of different segments, such as the Stack, Heap, Data and Code segments. Each of these segments is used to store different types of data and has specific characteristics.

28
Q

Stack memory

A

used for automatic storage duration variables, such as local variables and function call data. Stack memory is managed by the compiler, and it’s allocation and deallocation are done automatically. The stack memory is also a LIFO (Last In First Out) data structure, meaning that the most recent data allocated is the first to be deallocated.

void functionExample() {
int x = 10; // x is stored in the stack memory
}

29
Q

Heap memory

A

used for dynamic storage duration variables, such as objects created using the new keyword. The programmer has control over the allocation and deallocation of heap memory using new and delete operators. Heap memory is a larger pool of memory than the stack, but has a slower access time.

void functionExample() {
int* p = new int; // dynamically allocated int in heap memory
*p = 10;
// more code
delete p; // deallocate memory
}

30
Q

Data Segment

A

composed of two parts: the initialized data segment and the uninitialized data segment. The initialized data segment stores global, static, and constant variables with initial values, whereas the uninitialized segment stores uninitialized global and static variables.

// Initialized data segment
int globalVar = 10; // global variables
static int staticVar = 10; // static local variables
const int constVar = 10; // constant variables with value

// Uninitialized data segment
int globalVar; // uninitialized global variables

31
Q

Code segment

A

(also known as the Text segment) stores the executable code (machine code) of the program. It’s usually located in a read-only area of memory to prevent accidental modification.

32
Q

Static Storage Duration

A

Objects with static storage duration exist for the entire run of the program. These objects are allocated at the beginning of the program’s run and deallocated when the program terminates. Global variables, static data members, and static local variables fall into this category.

int global_var; // Static storage duration
class MyClass {
static int static_var; // Static storage duration
};
void myFunction() {
static int local_var; // Static storage duration
}

33
Q

Thread Storage Duration

A

Objects with thread storage duration exist for the lifetime of the thread they belong to. They are created when a thread starts and destroyed when the thread exits. Thread storage duration can be specified using the thread_local keyword.

thread_local int my_var; // Thread storage duration

34
Q

Automatic Storage Duration

A

Objects with automatic storage duration are created at the point of definition and destroyed when the scope in which they are declared is exited. These objects are also known as “local” or “stack” objects. Function parameters and local non-static variables fall into this category.

void myFunction() {
int local_var; // Automatic storage duration
}

35
Q

Dynamic Storage Duration

A

Objects with dynamic storage duration are created at runtime, using memory allocation functions such as new or malloc. The lifetime of these objects must be managed manually, as they are not automatically deallocated when the scope is exited. Instead, it is the programmer’s responsibility to destroy the objects using the delete or free functions when they are no longer needed, to avoid memory leaks.

int* ptr = new int; // Dynamic storage duration
delete ptr;

36
Q

Rule of Zero, Three, and Five

A

set of guidelines for managing object resources in modern C++, related to structures and classes. These rules deal with the default behavior of constructors, destructors, and other special member functions that are necessary for proper resource management.

37
Q

Rule of Zero

A

states that if a class or structure does not explicitly manage resources, it should not define any of the special member functions, i.e., destructor, copy constructor, copy assignment operator, move constructor, and move assignment operator. The compiler will automatically generate these functions, and the behavior will be correct for managing resources like memory and file handles.

Example:

struct MyResource {
std::string name;
int value;
};

38
Q

Rule of Three

A

states that a class or structure that manages resources should define the following three special member functions:

Destructor
Copy constructor
Copy assignment operator
These functions are necessary for proper resource management, such as releasing memory or correctly handling deep copies.

class MyResource {
public:
// Constructor and destructor
MyResource() : data(new int[100]) {}
~MyResource() { delete[] data; }

// Copy constructor
MyResource(const MyResource& other) : data(new int[100]) {
    std::copy(other.data, other.data + 100, data);
}

// Copy assignment operator
MyResource& operator=(const MyResource& other) {
    if (&other == this) { return *this; }
    std::copy(other.data, other.data + 100, data);
    return *this;
}

private:
int* data;
};

39
Q

Rule of Five

A

extends the Rule of Three to include two additional special member functions:

Move constructor
Move assignment operator
Modern C++ introduces move semantics, which allows for more efficient handling of resources by transferring ownership without necessarily copying all the data.

class MyResource {
public:
// Constructors and destructor
MyResource() : data(new int[100]) {}
~MyResource() { delete[] data; }

// Copy constructor
MyResource(const MyResource& other) : data(new int[100]) {
    std::copy(other.data, other.data + 100, data);
}

// Copy assignment operator
MyResource& operator=(const MyResource& other) {
    if (&other == this) { return *this; }
    std::copy(other.data, other.data + 100, data);
    return *this;
}

// Move constructor
MyResource(MyResource&& other) noexcept : data(other.data) {
    other.data = nullptr;
}

// Move assignment operator
MyResource& operator=(MyResource&& other) noexcept {
    if (&other == this) { return *this; }
    delete[] data;
    data = other.data;
    other.data = nullptr;
    return *this;
}

private:
int* data;
};

40
Q

Static Polymorphism

A

also known as compile-time polymorphism, is a type of polymorphism that resolves the types and method calls at compile time rather than at runtime. This is commonly achieved through the use of function overloading and templates in C++.

41
Q

Function overloading

A

is a way to create multiple functions with the same name but different parameter lists. The compiler determines the correct function to call based on the types and number of arguments used when the function is called.

42
Q

Templates

A

powerful feature in C++ that allows you to create generic functions or classes. The actual code for specific types is generated at compile time, which avoids the overhead of runtime polymorphism. The use of templates is the main technique to achieve static polymorphism in C++.

43
Q

Dynamic Polymorphism

A

programming concept in object-oriented languages like C++ where a derived class can override or redefine methods of its base class. This means that a single method call can have different implementations based on the type of object it is called on.

44
Q

Dynamic polymorphism is achieved through virtual functions, which are member functions of a base class marked with the virtual keyword.

A

When you specify a virtual function in a base class, it can be overridden in any derived class to provide a different implementation.

//Base class
class Shape {
public:
virtual void draw() { … }
}

//Derived class 1
class Circle : public Shape {
public:
void draw() override { … }
}

int main() {
Shape* shape;
Circle circle;

//store address of circle
shape = &circle;

// Call circle draw function
shape->draw();

45
Q

Virtual methods

A

key aspect of dynamic polymorphism in C++. They allow subclass methods to override the methods of their base class, so the appropriate method is called depending on the actual type of an object at runtime.

To declare a method as virtual, simply use the virtual keyword in the method’s declaration in the base class. This tells the compiler that the method should be treated as a virtual method, allowing it to be overridden by derived classes.

// Base class
class Shape {
public:
virtual double area() const {
return 0;
}
};

// Derived class
class Circle : public Shape {
public:
Circle(double r) : radius(r) {}

// Override the base class method
double area() const override {
    return 3.14 * radius * radius;
}

private:
double radius;
};

46
Q

Virtual Tables

A

mechanism used by C++ compilers to support dynamic polymorphism. In dynamic polymorphism, the appropriate function is called at runtime, depending on the actual object type.

When a class contains a virtual function, the compiler creates a virtual table for that class. This table contains function pointers to the virtual functions defined in the class. Each object of that class has a pointer to its virtual table (vptr, virtual pointer), which is automatically initialized by the compiler during object construction.

47
Q

Virtual Table example

A

class Base {
public:
virtual void function1() {
std::cout &laquo_space;“Base::function1” &laquo_space;std::endl;
}

virtual void function2() {
    std::cout << "Base::function2" << std::endl;
} };

class Derived : public Base {
public:
void function1() override {
std::cout &laquo_space;“Derived::function1” &laquo_space;std::endl;
}

void function3() {
    std::cout << "Derived::function3" << std::endl;
} };

int main() {
Base* obj = new Derived(); // create a Derived object and assign a pointer of type Base*
obj->function1(); // calls Derived::function1, due to dynamic polymorphism
obj->function2(); // calls Base::function2

delete obj;
return 0; }

when a Derived object is created, the compiler generates a Vtable for Derived class, containing pointers to its virtual functions:

Derived::function1 (overridden from Base)
Base::function2 (inherits from Base)
The vptr pointer in the Derived object points to this Vtable. When the function1 is called on the Base pointer pointing to the Derived object, the function pointer in the Vtable is used to call the correct function (in this case, Derived::function1). Similarly, the call to function2 calls Base::function2, since it’s the function pointer stored in the Vtable for Derived class.

Note that function3 is not part of the Vtable, as it is not a virtual function.

48
Q

Multiple Inheritance

A

feature in C++ where a class can inherit characteristics (data members and member functions) from more than one parent class. The concept is similar to single inheritance (where a class inherits from a single base class), but in multiple inheritance, a class can have multiple base classes.

When a class inherits multiple base classes, it becomes a mixture of their properties and behaviors, and can override or extend them as needed.

49
Q

syntax to declare a class with multiple inheritance:

A

class DerivedClass : access-specifier BaseClass1, access-specifier BaseClass2, …
{
// class body
};

The DerivedClass will inherit members from both BaseClass1 and BaseClass2. The access-specifier (like public, protected, or private) determines the accessibility of the inherited members.

// Derived class inheriting from both Animal and Mammal
class Dog : public Animal, public Mammal
{
public:
void bark()
{
std::cout &laquo_space;“I can bark! Woof woof!” &laquo_space;std::endl;
}
};

50
Q

In some cases, multiple inheritance can lead to complications such as ambiguity and the “diamond problem”.

A

Ensure that you use multiple inheritance judiciously and maintain well-structured and modular classes to prevent issues.

51
Q

Diamond Inheritance

A

specific scenario in multiple inheritance where a class is derived from two or more classes, which in turn, are derived from a common base class. It creates an ambiguity that arises from duplicating the common base class, which leads to an ambiguous behavior while calling the duplicate members.
To resolve this ambiguity, you can use virtual inheritance. A virtual base class is a class that is shared by multiple classes using virtual keyword in C++. This ensures that only one copy of the base class is inherited in the final derived class, and thus, resolves the diamond inheritance problem.

class Base {
public:
void print() {
cout &laquo_space;“Base class” &laquo_space;endl;
}
};

class Derived1 : virtual public Base {
public:
void derived1Print() {
cout &laquo_space;“Derived1 class” &laquo_space;endl;
}
};

class Derived2 : virtual public Base {
public:
void derived2Print() {
cout &laquo_space;“Derived2 class” &laquo_space;endl;
}
};

class Derived3 : public Derived1, public Derived2 {
public:
void derived3Print() {
cout &laquo_space;“Derived3 class” &laquo_space;endl;
}
};

int main()
{
Derived3 d3;
d3.print(); // Now, there is no ambiguity in calling the base class function
d3.derived1Print();
d3.derived2Print();
d3.derived3Print();

In the code above, Derived1 and Derived2 are derived from the Base class using virtual inheritance. So, when we create an object of Derived3 and call the print() function from the Base class, there is no ambiguity, and the code executes without any issues.

52
Q

RTTI (Run-Time Type Identification)

A

feature in C++ that allows you to obtain the type information of an object during program execution. This can be useful when using dynamic typing, where the type of an object can change at runtime.

There are two main mechanisms for RTTI in C++:

typeid operator
dynamic_cast operator

53
Q

typeid operator

A

include <typeinfo></typeinfo>

operator that returns a reference to an object of type std::type_info, which contains information about the type of the object. The header file <typeinfo> should be included to use typeid.</typeinfo>

class Base { virtual void dummy() {} };
class Derived : public Base { /* … */ };

int main() {
Base* base_ptr = new Derived;

// Using typeid to get the type of the object
std::cout << "Type: " << typeid(*base_ptr).name() << '\n';

delete base_ptr;
54
Q

dynamic_cast operator

A

type-casting operator that performs a runtime type check and safely downcasts a base pointer or reference to a derived pointer or reference. It returns null or throws a bad_cast exception (if casting references) when the casting fails.

class Base { virtual void dummy() {} };
class Derived1 : public Base { /* … / };
class Derived2 : public Base { /
… */ };

int main() {
Base* base_ptr = new Derived1;

// Using dynamic_cast to safely downcast the pointer
Derived1* derived1_ptr = dynamic_cast<Derived1*>(base_ptr);
if (derived1_ptr) {
    std::cout << "Downcast to Derived1 successful\n";
}
else {
    std::cout << "Downcast to Derived1 failed\n";
}

Derived2* derived2_ptr = dynamic_cast<Derived2*>(base_ptr);
if (derived2_ptr) {
    std::cout << "Downcast to Derived2 successful\n";
}
else {
    std::cout << "Downcast to Derived2 failed\n";
}

delete base_ptr;