RoadMap notes 3 Flashcards
Idioms, standards, debuggers, compilers, build systems, packafe managers, libraries
RAII (Resource Acquisition Is Initalization)
popular idiom in C++ that focuses on using the object’s life cycle to manage resources. It encourages binding the resource lifetime to the scope of a corresponding object so that it’s automatically acquired when an object is created and released when the object is destroyed. This helps in simplifying the code, avoiding leaks and managing resources efficiently.
example of using RAII to manage resources, specifically a dynamically allocated array:
class ManagedArray {
public:
ManagedArray(size_t size) : size_(size), data_(new int[size]) {
}
~ManagedArray() { delete[] data_; } // Access function int& operator [](size_t i) { return data_[i]; }
private:
size_t size_;
int* data_;
};
Usages:
{
ManagedArray arr(10);
arr[0] = 42;
// No need to explicitly free memory, it will be automatically released when arr goes out of scope. }
RAII, Another common use case is managing a mutex lock:
class Lock {
public:
Lock(std::mutex& mtx) : mutex_(mtx) {
mutex_.lock();
}
~Lock() { mutex_.unlock(); }
private:
std::mutex& mutex_;
};
Usages:
std::mutex some_mutex;
void protected_function() {
Lock lock(some_mutex);
// Do some work that must be synchronized // No need to explicitly unlock the mutex, it will be automatically unlocked when lock goes out of scope. }
Pimpl Idiom (pointer-to-implementation)
idiom, also known as a private class data, compiler firewall, or handle classes, is a technique used in C++ to hide the implementation details of a class by using a forward declaration to a private structure or class, keeping the public interface of the class clean, and reducing compile-time dependencies.
PimpI idiom example
class MyClass
{
public:
MyClass();
~MyClass();
void some_method();
private:
MyClass_Impl *pimpl; // pointer to the implementation
};
class MyClass_Impl // the actual implementation
{
public:
void some_method()
{
std::cout «_space;“Implementation method called!” «_space;std::endl;
}
};
MyClass::MyClass() : pimpl(new MyClass_Impl()) {} // constructor
MyClass::~MyClass() { delete pimpl; } // destructor
void MyClass::some_method()
{
pimpl->some_method(); // delegation to the implementation
}
all the public methods of MyClass will delegate the calls to the corresponding methods of MyClass_Impl. By doing this, you can hide the details of class implementation, reduce the compile-time dependencies, and ease the maintenance of your code.
CRTP (Curiously Recurring Template Pattern)
idiom that involves a class template being derived from its own specialization. This pattern allows for the creation of static polymorphism, which differs from regular runtime polymorphism that relies on virtual functions and inheritance.
CRTP is usually employed when you want to customize certain behavior in the base class without adding the overhead of a virtual function call. In short, CRTP can be used for achieving compile-time polymorphism without the runtime performance cost.
template <typename>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}</typename>
void implementation() { std::cout << "Default implementation in Base" << std::endl; } };
class Derived1 : public Base<Derived1> {
public:
void implementation() {
std::cout << "Custom implementation in Derived1" << std::endl;
}
};</Derived1>
class Derived2 : public Base<Derived2> {
// No custom implementation, so Base::implementation will be used.
};</Derived2>
the Base class is a template that takes a single type parameter. Derived1 and Derived2 are derived from their respective specialization of Base. CRTP is employed to allow custom implementations of the implementation() function in derived classes while providing a default behavior in the Base class. The interface() function in the Base class is a template for the derived class’s behavior and calls the corresponding implementation() function based on the static type.
This pattern enables you to override certain behavior in derived classes with additional functionality, all while avoiding the overhead of virtual function calls and, in turn, achieving a higher degree of efficiency at runtime.
Non-copyable idiom
C++ design pattern that prevents objects from being copied or assigned. It’s usually applied to classes that manage resources, like file handles or network sockets, where copying the object could cause issues like resource leaks or double deletions.
To make a class non-copyable, you need to delete the copy constructor and the copy assignment operator. This can be done explicitly in the class declaration, making it clear to other programmers that copying is not allowed.
Apply the non-copyable idiom to a class
lass NonCopyable {
public:
NonCopyable() = default;
~NonCopyable() = default;
// Delete the copy constructor
NonCopyable(const NonCopyable&) = delete;
// Delete the copy assignment operator
NonCopyable& operator=(const NonCopyable&) = delete;
};
To use the idiom, simply inherit from the NonCopyable class:
class MyClass : private NonCopyable {
// MyClass is now non-copyable
};
This ensures that any attempt to copy or assign objects of MyClass will result in a compilation error, thus preventing unwanted behavior.
Erase-remove idiom
common C++ technique to efficiently remove elements from a container, particularly from standard sequence containers like std::vector, std::list, and std::deque. It leverages the standard library algorithms std::remove (or std::remove_if) and the member function erase().
The idiom consists of two steps:
std::remove (or std::remove_if) moves the elements to be removed towards the end of the container and returns an iterator pointing to the first element to remove.
container.erase() removes the elements from the container using the iterator obtained in the previous step.
Erase-remove idiom example
std::vector<int> numbers = {1, 3, 2, 4, 3, 5, 3};</int>
// Remove all occurrences of 3 from the vector. numbers.erase(std::remove(numbers.begin(), numbers.end(), 3), numbers.end()); for (int number : numbers) { std::cout << number << " "; }
Copy-swap
C++ idiom that leverages the copy constructor and swap function to create an assignment operator. It follows a simple, yet powerful paradigm: create a temporary copy of the right-hand side object, and swap its contents with the left-hand side object.
Here’s a brief summary:
Copy: Create a local copy of the right-hand side object. This step leverages the copy constructor, providing exception safety and code reuse.
Swap: Swap the contents of the left-hand side object with the temporary copy. This step typically involves swapping internal pointers or resources, without needing to copy the full contents again.
Destruction: Destroy the temporary copy. This happens upon the exit of the assignment operator.
class String {
// … rest of the class …
String(const String& other); void swap(String& other) { using std::swap; // for arguments-dependent lookup (ADL) swap(size_, other.size_); swap(buffer_, other.buffer_); } String& operator=(String other) { swap(other); return *this; } };
Using the copy-swap idiom:
The right-hand side object is copied when passed by value to the assignment operator.
The left-hand side object’s contents are swapped with the temporary copy.
The temporary copy is destroyed, releasing any resources that were previously held by the left-hand side object.
This approach simplifies the implementation and provides strong exception safety, while reusing the copy constructor and destructor code.
Copy-Write Idiom
include <memory></memory>
sometimes called the Copy-on-Write (CoW) or “lazy copying” idiom, is a technique used in programming to minimize the overhead of copying large objects. It helps in reducing the number of actual copy operations by using shared references to objects and only copying the data when it’s required for modification.
class MyString {
public:
MyString(const std::string &str) : data(std::make_shared<std::string>(str)) {}
// Use the same shared data for copying. MyString(const MyString &other) : data(other.data) { std::cout << "Copied using the Copy-Write idiom." << std::endl; } // Make a copy only if we want to modify the data. void write(const std::string &str) { // Check if there's more than one reference. if(!data.unique()) { data = std::make_shared<std::string>(*data); std::cout << "Copy is actually made for writing." << std::endl; } *data = str; }
private:
std::shared_ptr<std::string> data;
};
C++11 and C++14
C++11 The C++11 standard, also known as C++0x, was officially released in September 2011. It introduced several new language features and improvements, including:
Auto: Allows compiler to infer the variable type based on its initializing expression.
auto integer = 42; // integer is of int type
auto floating = 3.14; // floating is of double type
Range-Based for Loop: Provides foreach-like semantics for iterating through a container or array.
std::vector<int> numbers {1, 2, 3, 4};
for (int number : numbers) {
std::cout << number << std::endl;
}
Lambda Functions: Anonymous functions that allow the creation of function objects more easily.</int>
auto add = -> int { return a + b; };
int sum = add(42, 13); // sum is equal to 55
nullptr: A new keyword to represent null pointers, more type-safe than using a literal ‘0’ or “NULL”.
int *ptr = nullptr;
Thread Support Library: Provides a standard way to work with threads and synchronize data access across threads.
std::thread t( { std::cout «_space;“Hello from another thread\n”; });
t.join();
C++14 The C++14 standard was officially released in December 2014 as a small extension over C++11, focusing more on fine-tuning language features and fixing issues. Some of the new features introduced:
Generic Lambdas: Allows lambda function parameters to be declared with ‘auto’ type placeholders.
auto add = { return a + b; };
auto sum_i = add(42, 13); // Still works with integers
auto sum_f = add(3.14, 2.72); // Now works with doubles too
Binary Literals: Allow you to input integers as binary literals for better readability.
int b = 0b110101; // Decimal value is 53
decltype(auto): Deduces the type of variable to match that of the expression it is initialized with.
auto func = { return a * b; };
decltype(auto) result = func(5, 3.14); // decltype(auto) deduces to “double”
Variable Templates: Allows you to define variables with template parameters.
template <typename>
constexpr T pi = T(3.1415926535897932385);
float r = pi<float>; // Instantiated as a float
double d = pi<double>; // Instantiated as a double</double></float></typename>
C++17
C++17, also known as C++1z, is the version of the C++ programming language published in December 2017. It builds upon the previous standard, C++14, and adds various new features and enhancements to improve the language’s expressiveness, performance, and usability.
Key Features:
If-init-statement: Introduces a new syntax for writing conditions with scope inside if and switch statements.
if(auto it = map.find(key); it != map.end())
{
// Use it
}
Structured Binding Declarations: Simplify the process of unpacking a tuple, pair, or other aggregate types.
map<string, int> data;
auto [iter, success] = data.emplace(“example”, 42);
Inline variables: Enables inline keyword for variables and allows single definition of global and class static variables in header files.
inline int globalVar = 0;
Folds expressions: Introduce fold expressions for variadic templates.
template <typename...>
auto sum(Ts... ts)
{
return (ts + ...);
}
constexpr if statement: Allows conditional compilation during compile time.
template <typename>
auto get_value(T t)
{
if constexpr (std::is_pointer_v<T>)
{
return *t;
}
else
{
return t;
}
}
Improved lambda expression: Allows lambda to capture a single object without changing its type or constness.
auto func = [x = std::move(obj)] { /* use x */ };
Standard file system library: std::filesystem as a standardized way to manipulate paths, directories, and files.</T></typename></typename...>
New Standard Library additions: <string_view> (non-owning string reference), <any> (type-safe discrimination union), <optional> (optional value wrapper), <variant> (type-safe sum type), and <memory_resource> (library for polymorphic allocators).</memory_resource></variant></optional></any></string_view>
Parallel Algorithms: Adds support for parallel execution of Standard Library algorithms.
This is a brief summary of the key features of C++17; it includes more features and library updates. For a complete list, you can refer to the full list of C++17 features and changes.