Introduction

Develop, train and test your skills in C++ language

Table of Contents

TODO: I will need to install a plugin or something. I plan to do it similar to this

Basics

Global variable

Like really global global variable that is defined in one compilation unit but can be changes in another place. It's one of those cases when "I know what I'm doing"

somewhere.h

extern int global_variable;

somewhere.cpp

int global_variable = 1;

another_place.cpp

void foo()
{
    global_variable = 2;
}

Heap VS stack

Constness

Arrays and pointers

Intact values

Values of the array cannot be changed. But array itself can be reassigned

int main(int argc, char const *argv[])
{
    const char* const_variable = nullptr;
    const_variable = new char[5];
    // const_variable[0] = 'H'; // error: expression must be a modifiable lvalue
    delete[] const_variable;
    return 0;
}

Intact pointer

This preserves the pointer but not the values

int main(int argc, char const *argv[])
{
    char* const const_variable = nullptr;
    // const_variable = new char[5]; // error: expression must be a modifiable lvalue
    const_variable[0] = 'H';
    delete[] const_variable;
    return 0;
}

So it must be initialized (assigned) only once. You cannot change size of the array, but content can be modified.

int main(int argc, char const *argv[])
{
    char* const const_variable = new char[5];
    const_variable[0] = 'H';
    const_variable[1] = 'e';
    const_variable[2] = 'l';
    const_variable[3] = 'l';
    const_variable[4] = 'o';
    delete const_variable;
    // cons[]t_variable = nullptr; // error: expression must be a modifiable lvalue
    return 0;
}

Read-only

Here's the absolute read-only version

int main(int argc, char const *argv[])
{
    const char* const const_variable = new char[5];
    // const_variable[0] = 'H'; // error: expression must be a modifiable lvalue
    delete[] const_variable;
    // const_variable = nullptr; // error: expression must be a modifiable lvalue
    return 0;
}

When function changes the pointer

How to make sure that a pointer you have is not going to change? For example, if the function reads elements from an array and will update the pointer to the position where it finished reading.

unsigned char* key = NULL;
size_t size = key_size;
OSSL_DECODER_from_data(dctx, key, &size);
// int OSSL_DECODER_from_data(OSSL_DECODER_CTX *ctx, const unsigned char **pdata, size_t *pdata_len);

Wrong

const unsigned char **data = const_cast<const unsigned char**>(&key); // data pointer will be changed
OSSL_DECODER_from_data(dctx, data, &size);

Correct

const unsigned char *data = const_cast<const unsigned char*>(key);
OSSL_DECODER_from_data(dctx, &data, &size);

What is const in const unsigned char *data vs const unsigned char **data?

Fork

I believe this bog post explains in detail about fork and wait, but I just want my small example here.

Linux only

Execution sequence is interesting in this case

#include <iostream>
#include <sys/wait.h> // wait
#include <unistd.h> // fork, exec

const char* const long_argv[] = {"./long_process.sh", NULL};

int main(int argc, char const *argv[])
{
    if (fork() == 0) {
        std::cout << "child process" << std::endl;
        execvp(long_argv[0], const_cast<char* const *>(long_argv));
    } else {
        std::cout << "parent process" << std::endl;
    }
    int status;
    wait(&status);
    std::cout << "parent done" << std::endl;

    return 0;
}
#!/bin/bash

for i in {1..10}; do
    echo $i
    sleep 1
done

You should be carefull when call functions like system or fork itself in applications that consume a lot of memory (like games) because fork will clone the application process first which wil require free space equal at least to current needs of the application.

Strings

Strip whitespaces from the right

std::string line;
if (std::getline(file, line)) {
    line.erase(std::find_if(line.rbegin(), line.rend(), [](unsigned char ch) {
        return !std::isspace(ch);
    }).base(), line.end());
}

Files

File path

#include <filesystem>

static std::ifstream open_file(const std::string& folder_name, const std::string& file_name) {
    std::filesystem::path file_path = std::filesystem::path(root);
    file_path /= folder_name;
    file_path /= file_name;
    return std::ifstream(file_path, std::ios::binary);
}

Get content as string

std::ifstream in(filename, std::ios::in | std::ios::binary);
if (!in) {
    std::cerr << "could not open " << filename << std::endl;
    return 1;
}
std::ostringstream contents;
contents << in.rdbuf();
std::string data = contents.str();

Get binary content as buffer

bool load_file(const char* filename, unsigned char** buf, int& size)
{
    if (std::ifstream file_stream{filename, std::ios::binary | std::ios::ate}) {
        size = file_stream.tellg();
        std::string str(size, '\0');
        file_stream.seekg(0);
        if (file_stream.read(&str[0], size)) {
            *buf = static_cast<unsigned char*>(malloc(size));
            str.copy(reinterpret_cast<char*>(*buf), size);
            return true;
        }
        std::cerr << "Reading of file " << filename << " failed" << std::endl;
        return false;
    }
    std::cerr << "Can't open file " << filename << std::endl;
    return false;
}

Get file size

TODO

Pointers

Smart pointers

Elegant code from this github issue. Look how destructor is called in the style of defer-ed functions when the object goes out of scope.

int main() {
    std::string filename = "test.pem";
    std::string password = "password";
    {
        std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> key(EVP_RSA_gen(2048), ::EVP_PKEY_free);

        std::unique_ptr<BIO, decltype(&::BIO_free)> out(BIO_new(BIO_s_file()), ::BIO_free);
        if (out != nullptr) {
            if (BIO_write_filename(out.get(), const_cast<char *>(filename.c_str())) > 0) {
                int size = PEM_write_bio_PrivateKey(out.get(), key.get(), nullptr, nullptr, 0,
                                                    nullptr, const_cast<char *>(password.data()));
                std::cout << size << std::endl;
            }
        }
    }
    {
        std::string invalidPassword = "invalid";
        std::unique_ptr<BIO, decltype(&::BIO_free)> in(BIO_new(BIO_s_file()), ::BIO_free);
        if (in != nullptr) {
            if (BIO_read_filename(in.get(), filename.c_str()) > 0) {
                EVP_PKEY *pkey = PEM_read_bio_PrivateKey(in.get(), nullptr, nullptr, nullptr);
                std::cout << (pkey == nullptr) << std::endl;
            }
        }
    }
}

Pointing arguments

What if you have a pointer defined in one place and you want to assign some value in a special function? How to pass that pointer to the function and receive and

#include <iostream>

struct SimpleStruct {
    int bar;
};

void foo(SimpleStruct* s) {
    s = new SimpleStruct;
    s->bar = 42;
}

int main(int argc, char const *argv[])
{
    SimpleStruct* x = nullptr;
    foo(x);
    std::cout << x->bar << std::endl;
    return 0;
}

C style

In C one would use a pointer for that

#include <iostream>

struct SimpleStruct {
    int bar;
};

void foo(SimpleStruct** s) {
    *s = new SimpleStruct;
    (*s)->bar = 42;
}

int main(int argc, char const *argv[])
{
    SimpleStruct* x = nullptr;
    foo(&x);
    std::cout << x->bar << std::endl;
    delete x;
    return 0;
}

By reference

But in C++ it is logical to use a reference, innit?

#include <iostream>

struct SimpleStruct {
    int bar;
};

void foo(SimpleStruct* &s) {
    s = new SimpleStruct;
    s->bar = 42;
}

int main(int argc, char const *argv[])
{
    SimpleStruct* x = nullptr;
    foo(x);
    std::cout << x->bar << std::endl;
    delete x;
    return 0;
}

C++ style

But the clean way would be to use smart pointers

#include <iostream>
#include <memory>

struct SimpleStruct {
    int bar;
};

void foo(std::unique_ptr<SimpleStruct>& s) {
    s = std::make_unique<SimpleStruct>();
    s->bar = 42;
}

int main(int argc, char const *argv[])
{
    std::unique_ptr<SimpleStruct> x;
    foo(x);
    std::cout << x->bar << std::endl;
    return 0;
}

Leaks

Here's a similar situation to Pointing arguments but the function is destructing the object and cleaning after itself.

In this example it will not succeed

#include <iostream>

struct SimpleStruct {
    int bar;
};

void foo(SimpleStruct* s) {
    std::cout << s->bar << std::endl;
    delete s;
    s = nullptr;
}

int main(int argc, char const *argv[])
{
    SimpleStruct* x = new SimpleStruct;
    x->bar = 42;
    foo(x);
    if (x) std::cout << x->bar << std::endl;
    return 0;
}

Raw pointers

Having proper destructors and an STD container with raw pointers will not save one from leaks.

if the elements are pointers, the pointed-to objects are not destroyed.

CPP reference

#include <algorithm>
#include <iostream>
#include <vector>

class GoodData {
public:
    GoodData(int id) : _id(id) {
        std::cout << "data [" << _id << "] constructor" << std::endl;
    }
    ~GoodData() {
        std::cout << "data [" << _id << "] destructor" << std::endl;
    }
private:
    int _id;
};

int main(int argc, char const *argv[])
{
    const int n_data = 5;
    {
        std::vector<GoodData*> array_of_good_data(n_data);
        std::generate(array_of_good_data.begin(),
            array_of_good_data.end(),
            [i = 0]() mutable { return new GoodData(i++); });
    }
    // leak?
    return 0;
}

Weird Map

#include <iostream>
#include <string>
#include <unordered_map>

class InterestingData {
public:
    InterestingData() {} // Ok, fine, here you go
    InterestingData(int* id) : _id(id) {
        std::cout << "constructor" << *_id << std::endl;
    }
    ~InterestingData() {
        std::cout << "destructor" << *_id << std::endl;
        delete _id;
    }
    InterestingData(const InterestingData& other) : _id(new int(*other._id)) {
        std::cout << "copy constructor" << *_id << std::endl;
    }
    InterestingData& operator=(const InterestingData& other) {
        _id = new int(*other._id);
        std::cout << "copy assignment" << *_id << std::endl;
        return *this;
    }

    int* _id;
};

int main(int argc, char const *argv[])
{
    std::unordered_map<std::string, InterestingData> map;
    InterestingData data1(new int(1)), data2(new int(2));
    map["test"] = data1;
    map["test"] = data2; // LEAK!
    map.clear();
    std::cout << "BOOM!" << std::endl;
    return 0;
}

OOP

Destructors

What bad can happen if one doesn't define a destructor in the base class as virtual?

Just virtual destructor

#include <iostream>

class ProtectedDestructor {
public:
    ProtectedDestructor() {
        std::cout << "[ProtectedDestructor] constructor" << std::endl;
    }

    virtual ~ProtectedDestructor() {
        std::cout << "[ProtectedDestructor] destructor" << std::endl;
    }
};

class PleaseLetMeDelete : public ProtectedDestructor {
public:
    PleaseLetMeDelete() {
        std::cout << "[PleaseLetMeDelete] constructor" << std::endl;
    }
    ~PleaseLetMeDelete() {
        std::cout << "[PleaseLetMeDelete] destructor" << std::endl;
    }
};

int main(int argc, char const *argv[])
{
    ProtectedDestructor* obj = new PleaseLetMeDelete();
    delete dynamic_cast<PleaseLetMeDelete*>(obj);
    // delete obj;
    return 0;
}

Virtual and Protected destructor

#include <iostream>

class ProtectedDestructor {
public:
    ProtectedDestructor() {
        std::cout << "[ProtectedDestructor] constructor" << std::endl;
    }
protected:
    virtual ~ProtectedDestructor() {
        std::cout << "[ProtectedDestructor] destructor" << std::endl;
    }
};

int main(int argc, char const *argv[])
{
    const ProtectedDestructor* obj = new ProtectedDestructor();
    // delete dynamic_cast<PleaseLetMeDelete*>(obj);
    // delete obj; // error: destructor is inaccessible
    return 0;
}

Non Copyable object

Non Copyable Macro

#include <vector>

#define MACRO_NONCOPYABLE(ClassName) \
    ClassName(const ClassName&) = delete; \
    ClassName& operator=(const ClassName&) = delete;

class NonCopyable {
protected:
    NonCopyable() = default;
    ~NonCopyable() = default;
public:
    MACRO_NONCOPYABLE(NonCopyable)
};

class TestClass : public NonCopyable {
public:
    TestClass() = default;
    ~TestClass() = default;
};

int main(int argc, char const *argv[])
{
    std::vector<TestClass> v;
    TestClass t;
    v.push_back(std::move(t)); // <-- compile error here
    return 0;
}

Virtual Call

STD

Regular exressions

String Views

Threads

Templates

enable if

Patterns

State Machine

Factory

We would want to write a tool that can provide similar functionality between components with different implementations. It should be easily expandable with new components that doesn't require to change anything in the main app.

For this task we are going to use a factory pattern.

Interface

AbstractTime.h

class AbstractTime {
public:
    virtual AbstractTime* build() = 0;
    virtual std::time_t get() const = 0;
    virtual void set(std::time_t time) = 0;
    virtual std::string name() const = 0;
};

Factory

Factory itself will be a singleton. And essentially it's a collection of build functions. And we will be calling register_builder in an interesting way (check for constructors in concrete objects).

TimeFactory.h

using BuildFunction = std::function<AbstractTime*()>;

class TimeFactory {
public:
    static TimeFactory* instance() {
        if (!factory) {
            factory = new TimeFactory();
        }
        return factory;
    }
    static void register_builder(const BuildFunction& builder) {
        builders.push_back(builder);
    }
    static std::vector<BuildFunction>::iterator iterator_begin() {
        return builders.begin();
    }
    static std::vector<BuildFunction>::iterator iterator_end() {
        return builders.end();
    }
private:
    static TimeFactory* factory;
    static std::vector<BuildFunction> builders;
};

TimeFactory.cpp

#include "TimeFactory.h"

TimeFactory* TimeFactory::factory = nullptr;
std::vector<BuildFunction> TimeFactory::builders = std::vector<BuildFunction>();

Define components

Implement the interface

class MotherboardRTC : public AbstractTime {
public:
    MotherboardRTC() {
        TimeFactory::register_builder(std::bind(&MotherboardRTC::build, this));
    }
    AbstractTime* build() override {
        return this;
    }

And now just define a stati object. But this line actually calls register_builder in a static object of TimeFactory class. So this object will be available in the main function.

static MotherboardRTC motherboard_rtc;

Use components

    std::map<std::string, AbstractTime*> loaded_time;
    for (auto i = TimeFactory::iterator_begin(); i != TimeFactory::iterator_end(); ++i) {
        auto build_function = *i;
        auto time = build_function();
        loaded_time[time->name()] = time;
    }

Result

./time_factory list
# random
# system

./time_factory get system
# system: 08/26/24 20:43:02 Pacifique (heure dТete)

Network

Time

Scheduler

It runs on even intervals between execution. Like the main loop in game engines - no matter how long it takes to process all components in the hiaerarchy in a single moment in time, the next moment will happen exactly after the scheduled delay.

Main loop

And this is useless for now but precise worker that repeats tasks at moments separated by the delay, skipping time required to execute tasks. So no matter how long tasks will take (if it less then the delay), the starting moment for tasks is exact

  void main_loop() {
    bool running = true;
    TimePoint prev_now = Clock::now();
    uint64_t step = 0;
    using std::chrono::operator""ms;
    auto step_delay = 1000ms;

    while (running) {
      TimePoint now = Clock::now();
      auto drift = std::chrono::duration_cast<Duration>(
              now - prev_now - step_delay).count();
      std::cout << "-- drift "  << drift << "ms " << std::endl;
      prev_now = now;

      run_tasks();

      TimePoint after_tasks = Clock::now();
      auto task_time = std::chrono::duration_cast<Duration>(
              after_tasks - now).count();
      std::cout << "-- task time "  << task_time << "ms " << std::endl;
      
      ++step;

      auto expected_time = now + step_delay;
      std::this_thread::sleep_until(expected_time);
    }
  }

Random

Serialization

RSA

You cannot do a "private encrypt" operation using EVP_PKEY_encrypt. That function only does a public encrypt operation. Similarly you cannot do "public decrypt" with EVP_PKEY_decrypt.

In reality a "private encrypt" operation is more commonly known as a signature. You need to use EVP_PKEY_sign for that. A "public decrypt" operaiton is more commonly known as a verify. You need to use EVP_PKEY_verify for that.

SHA256

RSA