Segmentation faults
When a program tries to access a memory location outside its allowed segment, the operating system blocks it, causing a segmentation fault. More specifically, the operating system manages memory using a hardware component inside CPU known as MMU (Memory Management Unit). It uses it to control access to memory and enforce memory protection. Each program runs in its own virtual address space and MMU, using what is known as page tables, translates virtual addresss (used by programs) into physical addresses (actual loactions in RAM). When a program access an illegal memroy address, the MMU and OS work together to detect the error and stop the program.
- The program issues a memory access request (e.g process 102 tries to write to address 0xDEADBEEF, which is outside of its’ allowed segment).
- The MMU checks the memory access. (it compares the requested address with the page tables and if it isn’t allowed or is a protected area, it raises a page fault).
- The CPU triggers an exception (MMU tells CPU that illegal memory access happened and CPU generates an exception, also known as trap. specifically it raises a segmentation fault exception, also known as SIGSEGV signal in Unix/Linux).
- The OS receives the exception (the OS kernel gets the signal and checks if the memory access was valid and if the program has special permission to handle it)
- The OS terminates the program (if the memory access is illegal, the OS sends the SIGSEGV signal to the program. if the program does not have signal handler, than the OS forcefully stops (kills) the process).
The process/program is therefore forcefully stopped (“crashes”) therefore relevant data are lost and requests are not handled properly. It’s important to make sure that seg faults don’t happen, so that a program doesn’t crash and instead returns error messages for incorrect requests, etc. Usually the OS creates a core dump (core file), which is a snapshot of the program’s memory that the developer can investigate to understand the crash.
Here is an example of a SIGSEGV handler in C.
It assigns each process its own memory space, it prevents a program from Segmentation fault will unfortunetely crash the whole process
Common causes of segmentation faults.
#include <cstdlib>
// dereferencing NULL or uninitialized pointer
void common_segfault_1() {
int* num_ptr = nullptr;
*num_ptr = 5;
}
// accessing array beyond bounds
void common_segfault_2() {
int nums[4];
nums[15] = 5; // accessing
}
// using memory after freeing it
void common_segfault_3() {
int* ptr = (int*) malloc(sizeof(int));
free(ptr);
*ptr = 10;
}
// writing read-only memory
void common_segfault_4() {
char *str = "hello world";
str[0] = 'H';
}
int main(){
common_segfault_1();
common_segfault_2();
common_segfault_3();
common_segfault_4();
return 0;
}
Awesome header-only C++ libraries
This awesome-hpp repo contains the most popular header-only libraries. The most popular are argparse & cxxopts for parsing arguments, Catch2 for fast testing, taskflow for concurrency, csv parser & simdjson & rapidjson for parsing data formats, dlib for ML, fmt for formatting (printing containers, etc), concurrentqueue for lock-free queue, immer for immutable data structures and functional programming, backward-cpp for clearer debug errors, stb & sokol for geometry/graphics/games, GuiLite for GUI creation,cpp-httplib for HTTP/HTTPS functionalities, pybind11 for executing Python through C++, spdlog for fast logging, matplotlib-cpp for plotting with matplotlib framework, nameof for reflection and name and type of function, magic_enum for static reflection of enums, cereal for serealizing, GSL for Mircosoft’s Guidelines Support, ranges which is now included in C++20, crow is a microframework for web, asio for network and low-level I/O programming with a consistent asynchronous model, eigen for linear algebra.
Other popular libraries, that are not header-only though, are STL (which is similar to the standard library of C++ as this thread explains), Boost with smart pointers, regular expressions, multithreading, networking (many features are already in core C++), Poco with functionalities, opencv for computer vision, openssl which is the swiss army knife for cryptography, ffmpeg-cpp for audio and video streaming, QT that provides libraries for GUI development, json for JSON conversions, mosquitto which is pub/sub server, tensorflow for ML and gest for testing.
Compiler Explorer (godbolt), Compiler Flags, Libraries, etc
Godbolt is an insanely useful site to write snippets, run them and generally get an intuition of how the compiler works and transforms your code to assembely. The creator has two videos (here and here) to get you started.
It’s a good idea to start from scratch (press the button More -> Reset code and UI
).
The open a compiler and output box (you drag and drop them around and scale the letters). Then essential compiler flags -Wall -Wextra
that should include all the other important flags. The use -O1
/-O2
/-O3
to get the compiler optimise your code (a bit, mediocre or aggresively). You can also use -std=c++14
/-std=c++17
/-std=c++20
to define specific version of the language.
You can also use Catch2
library (use the libraries section in godbolt) and the #include <catch2/catch_test_macros.hpp>
to write a funciton and quick tests for that function. Example follows:
// select `Catch2 3.0.0-preview2` from Libraries section (upper right corner)
// select compiler `x86-64 clang 14.0.0`
// add compiler options `-Wall -Wextra -lCatch2Main -lCatch2`
#include <catch2/catch_test_macros.hpp>
// Type your code here, or load an example.
int square(int num) { return num * num; }
TEST_CASE("square root of 3", "[interger]") { REQUIRE(square(3) == 9); }
You can create permanent links for the code like this.
Variadic Templates (used in a cache example)
The following code makes a key (identifier for a cache). The interesting thing is that the function can take any number of parameters (vectors of any type or plain types) and will use delimeters .
and -
to split the vectors and different arguments. To use any number of parameters in C++, we use variadic templates.
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
// ========> generic_cache.h <========
/// Helper function to emit a value to a stream
template<typename T>
void toKey(std::ostringstream& os, const T& item)
{
os << item;
}
/// Helper function to emit dot-separated vector of values to a stream
template<typename T>
void toKey(std::ostringstream& os, const std::vector<T>& itemVec)
{
if (itemVec.size() == 1)
{
toKey(os, itemVec.front());
}
else
{
auto it = itemVec.begin(), end = itemVec.end(), last = end;
--last;
for (; it != end; ++it)
{
toKey(os, *it);
if (it != last)
os << ".";
}
}
}
/// Helper function to emit arguments from variadic pack - termination case
inline void emitProviderArg(std::ostringstream&)
{
}
/// Helper function to emit arguments from variadic pack.
template<typename ProviderArg, typename... Tail>
void emitProviderArg(std::ostringstream& os, ProviderArg arg, Tail... tail)
{
// Get head
toKey(os, arg);
// argument delimiter
os << "_";
// Call recursively with the tail of pack
emitProviderArg(os, tail...);
}
/**
* Compose cache identifier from a generic variadic pack `ProviderArgs`.
* Each variadic pack item could be a C++ scalar or vector (delimited by dots).
* Pack args are delimited by `_`
* Example:
* makeCacheIdentifier(std::vector<int>{42}, "foobar") -> "42_foobar_"
*/
template<typename... ProviderArgs>
std::string makeCacheIdentifier(ProviderArgs... providerArgs)
{
std::ostringstream oss;
emitProviderArg(oss, providerArgs...);
std::string identifier{std::move(oss).str()};
return identifier;
}
int main() {
std::vector<int> vec = {2,3,4};
std::cout << makeCacheIdentifier(std::vector({2,3,4}), "foo", std::vector({6,5})) << std::endl;
return 0;
}
Encoding / Decoding Essential Knowledge
It all started with ASCII, which used 1 byte per letter (8 bits and 2^8 = 256 combinations) but represented only 128 letters (from 0 to 127). The last bit could be used for anything, people were doing fancy stuff for different languages (and a mess started happening when sending messages from Greek to Hebrew for example). Unicode is a platonic character set (it assigns a number to each character and does not define how to store it). UTF-8, UTF-16, UTF-32 are encodings of these. UTF-8 uses variable-width encoding (and more specifically the english alphabet letter match ASCII numbering and need only 1 byte, while greek letter need 2+ bytes). There is also an issue with big/little endian that is fixed using a two byte prefix.
- C++ Character Encoding Explained
- Joel Spolsky - The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets
Using pandoc and latex expresion to create slides with markdown
Pandoc is a universal document converter. Take a markdown and make latex, pdf, pptx, etc. It’s super useful and quite flexible.
Recently I was reading some interesting papers, specifically about OCO (online convex learning). The following is a paragraph about the problem, that serves as template of how to use markdown to write mathematical formulas. Here and here are cheatsheet for math symbols in latex. Then using the command pandoc -t beamer text.md -o presentation.pdf
you can produce awesome professional slides. Don’t forget the following YAML at the header of the file for customization.
---
title: "Adaptive Online Non-stochastic Control Presentation"
date: "December, 2024"
author: "Georgios Giannakoulias"
institute: "For TU Delft"
theme: "Frankfurt"
output:
beamer_presentation:
keep_tex: true
---
OCO (online convex learning)
The OCO framework can be seen as a structured repeated game.
- There is a decision set: $\mathcal{K} \subseteq \mathbb{R}$. (convex set in Euclidean space) and at iteration $t$, the online player chooses $x_t \in \mathcal{K}$ .
- After the player has committed to this choice, a convex cost function $c_t \in \mathcal{F} : \mathcal{K} → R$ is revealed. The function is being chosen by an adversary.
- The cost of any solution $x \in \mathcal{K}$ until time $T$ is \(C_{\mathcal{x}}(T) = \sum_{t=1}^T c_t(x_t)\)
- An algorithm $\mathcal{A}$ maps a certain game history to a decision in the decision set. The goal is to create an algorithm $\mathcal{A}$ that minimizes regret \(Regret_{T}(\mathcal{A}) = C_{\mathcal{A}}(T) - \min_{x \in \mathcal{K}}{C_{x}(T)}\)
Parsing input in competitive programming (Python/C++)
Parsing input files is essential in competitive programming, in platforms like codeforces, adventofcode, etc. Here is a collection of tools/functions/ways to parse input in my favourite languages.
// using argument for the file
int main(int argc, char * argv[]) {
std::string input;
if (argc < 2) {
std::cout << "Provide the input file path as an argument" << std::endl;
return 1;
}
input = argv[1];
// ...
return 0;
}
// you could also provide a default file name to use
// std::getline(), std::string::substr and std::stoi
int main(int argc, char * argv[]) {
std::string line;
std::fstream file(input);
std::vector<int> nums;
while(std::getline(file, line)) {
// imagine line like `893 432`
std::size_t space_idx = line.find(' '); // play with indices
nums.push_back(std::stoi(line.substr(0, space_idx))); // [pos, pos+count)
nums.push_back(std::stoi(line.substr(space_idx+1,line.size() - space_idx)));
// assume indices [start, end), the formula is `substr(start, end-start);`
// assume indices [start, end], the formula is `substr(start, end-start+1);`
}
// ...
}
// you can also use a delimiter with getline, for more fine grained parsing
#include <iostream>
#include <sstream>
int main(){
// ...
std::string input = "abc,def,ghi";
std::istringstream ss(input);
std::string token;
while(std::getline(ss, token, ',')) {
std::cout << token << '\n';
}
// ...
}
// using double loop for two delimeters
int main(){
std::string line, token;
std::ifstream file("text.txt");
if(file.is_open()){
while(std::getline(file, line)){ // get a whole line
std::stringstream linestream(line);
while(getline(linestream, token, ',')){
// use token like (std::stoi(token))
}
}
}
}
// using stringstream and >>
std::string input = "...";
std::stringstream ss(input);
std::string word; // temporary token
std::vector<std::string> words;
int wordcount = 0;
while(ss >> word){
words.push_back(word);
wordcount++;
}
This link is super useful as well.
// reading lonely input
int N, M;
cin >> N >> M;
// read a sequence of number
// using the synergetic copy_n + istream_iterator + back_inserter trio
int length; cin >> length;
vector<int> sequence; sequence.reserve(length); // prepare vector to host “length” elements
copy_n(istream_iterator<int>(cin), length, back_inserter(sequence));
// with ranges the code streamlines
// bounded returns a range of exactly length elements
vector<int> boundedIn = view::bounded(istream_iterator<int>(cin), length);
// reading a matrix
int rows, cols; cin >> rows >> cols;
vector<vector<int>> matrix; matrix.resize(rows);
for (auto& row : matrix)
{
row.reserve(cols);
copy_n(istream_iterator<int>(cin), cols, back_inserter(row));
}
// fixing leading ‘\n’ issue with cin and getline
cin >> N >> std::ws;
getline(cin, line);
// printing answers with cout, avoid using std::endl to avoid flushing and enhance performance
cout << ans1 << " " << ans2 << "\n";
// When you need to print a sequence – like a vector – just use copy + ostream_iterator:
copy(begin(v), end(v), ostream_iterator<int>(cout)); // default separator is ' '
copy(begin(v), end(v)-1, ostream_iterator<int>(cout, ',')); // using comma seperator for all but last
cout << v.back(); // last
// in C++17, we have this smart syntax
copy(begin(v), end(v), make_ostream_joiner(cout, ','));
// print floating point numbers and the output is expected to be an approximation of a certain number of digits
cout << fixed << setprecision(2) << num1 << " " << num2;
// initialize before looping
cout << fixed << setprecision(2);
while (...)
{
//...
cout << ans; // formatted as you like
}
In python, the syntax is much more straightfoward and nice:
# you can change the following to read through files
FILE = False # if needed change it while submitting
if FILE:
sys.stdin = open('input.txt', 'r')
sys.stdout = open('output.txt', 'w')
# generic functions
def get_int():
return int(sys.stdin.readline())
def get_ints(dem=" "):
return map(int, sys.stdin.readline().strip().split(dem))
# a, b = get_ints("1 2") --> a = 1, b = 2
# a, *rest = get_ints("1 2") --> a = 1, b = [2]
def get_string():
return sys.stdin.readline().strip()
def get_strings(dem=" "):
return map(str, sys.stdin.readline().strip().split(dem))
# using regex
re.split('; |, |\*|\n', string_to_split) # multiple delimiters
# example from adventofcode 2021 day 4 (https://adventofcode.com/2021/day/4)
with open("file.txt") as data_file:
nums, *boards = data_file.read().split("\n\n")
nums = [int(n) for n in nums.split(",")]
boards = [[int(n) for n in board.split()] for board in boards]
For complicated problems, regex is the life saviour.
C++ Macros for consice clean code
Introducing the concept of ESMA (ESsential MAcros). This intorduces uniformity in a department, in terms of how to log erros (e.g. the line and filename, the names of the variables and values), throwing and catching errors with one-liners etc.
Introducing different kinds of throwing errors (godbolt link):
// defining ESMAs (ESsential MAcros)
// specifically a code position macro
#include <string>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <optional>
// ========> esma_codeposition.h <========
namespace esma {
struct CodePosition {
// Represents a position in a source code file.
public:
// CREATORS
CodePosition(std::string path, int line);
// Create a 'CodePosition' referring to the specified file 'path' and
// 'line' number.
// DATA
std::string d_file;
int d_line;
private:
// PRIVATE ACCESSORS
std::string filename(std::string path) const;
// extract file name from a path (the section after last slash).
// If full path ends with slash the whole path is returned.
};
std::ostream& operator<<(std::ostream& os, const CodePosition& v);
#define ESMA_CODEPOS() esma::CodePosition(__FILE__, __LINE__)
// A convenience macro to create a CodePosition with the __FILE__ and
// __LINE__ where the macro is called.
} // close esma package namespace
// ========> esma_codeposition.cpp <========
namespace esma {
CodePosition::CodePosition(std::string path, int line)
: d_file(filename(std::move(path)))
, d_line(line)
{}
std::string CodePosition::filename(std::string path) const
{
const size_t pos = path.find_last_of("/");
if (pos == std::string::npos || pos == path.length() - 1)
{
return path;
}
return path.substr(pos+1);
}
// FREE STREAM OPERATOR
std::ostream& operator<<(std::ostream& os, const CodePosition& v)
{
return os << "[" << v.d_file << ":" << v.d_line << "]";
}
} // close esma package namespace
// ========> esma_exceptionthrower.h <========
namespace esma {
///Example Usage: Throw a std::logic_error with multiple error message
// ESMA_THROW(std::logic_error,
// "A logic error has occured."
// "While handing something.");
//
// e.what() ==
// [somefile.cpp:###] A logic error has occured. While handing Somthing."
namespace detail {
template<typename Arg, typename ...Args>
std::string concat(Arg&& arg, Args&&... args)
{
std::ostringstream os;
os << std::forward<Arg>(arg);
(void) std::initializer_list<int>{
0, (os << ' ' << std::forward<Args>(args), 0)...};
return os.str();
}
} // namespace detail
#define ESMA_THROW(EXCEPTION, ...) \
throw EXCEPTION(esma::detail::concat( \
ESMA_CODEPOS() __VA_OPT__(,) \
__VA_ARGS__))
// Throw a EXCEPTION, with code position and all provided information set
// as EXCEPTION.what(). Note the EXCEPTION type must have a constructor
// takes a string what_arg defined.
#define ESMA_THROW_IF(PRED, EXCEPTION, ...) \
if (PRED) { \
ESMA_THROW(EXCEPTION, __VA_ARGS__); \
}
// Throw a EXCEPTION if PRED evaluate true, with code position and all
// provided information set as EXCEPTION.what(). Note the EXCEPTION type must
// have a constructor takes a string what_arg defined.
// the we can define macros for most common exception types
#define ESMA_RUNTIME_ERROR(...) ESMA_THROW(std::runtime_error, __VA_ARGS__)
#define ESMA_RUNTIME_ERROR_IF(PRED, ...) \
ESMA_THROW_IF(PRED, std::runtime_error, __VA_ARGS__)
// more erros: range_error, overflow_error, underflow_error, logic_error
// invalid_argument, domain_error, length_error, out_of_range
#define ESMA_VALUE_OR_THROW(OPTIONAL, EXCEPTION, ...) \
(OPTIONAL ? *OPTIONAL : ESMA_THROW(EXCEPTION, #OPTIONAL " is null" __VA_OPT__(, ) __VA_ARGS__)) \
#define ESMA_VALUE_OR_RUNTIME_ERROR(OPTIONAL, ...) \
ESMA_VALUE_OR_THROW(OPTIONAL, std::runtime_error, __VA_ARGS__)
// returns underlying value if set, else throws an exception. supports
// types that override bool/* operators, eg raw ptr, std::optional,
// bdlb::NullableValue. the exception clearly logs the file/line and arg's
// name.
//
// examples:
// assignment: `const int val = ESMA_VALUE_OR_RUNTIME_ERROR(getOptional());`
// exception msg: `[fxlt_exceptionthrower.u.t.cpp:461] getBslOptional() is null`
} // close esma package namespace
int main() {
std::cout << ESMA_CODEPOS();
ESMA_RUNTIME_ERROR_IF(0 == 1, "Unexpected equality");
// std::optional<int> apiResult; // throws in next line
std::optional<int> apiResult = 5;
auto age = ESMA_VALUE_OR_RUNTIME_ERROR(apiResult);
return 0;
}
Godbolt link for the following code:
// defining ESMAs (ESsential MAcros)
// specifically a code position macro
#include <string>
#include <iostream>
// ========> esma_codeposition.h <========
namespace esma {
struct CodePosition {
// Represents a position in a source code file.
public:
// CREATORS
CodePosition(std::string path, int line);
// Create a 'CodePosition' referring to the specified file 'path' and
// 'line' number.
// DATA
std::string d_file;
int d_line;
private:
// PRIVATE ACCESSORS
std::string filename(std::string path) const;
// extract file name from a path (the section after last slash).
// If full path ends with slash the whole path is returned.
};
std::ostream& operator<<(std::ostream& os, const CodePosition& v);
#define ESMA_CODEPOS() esma::CodePosition(__FILE__, __LINE__)
// A convenience macro to create a CodePosition with the __FILE__ and
// __LINE__ where the macro is called.
} // close esma package namespace
// ========> esma_codeposition.cpp <========
namespace esma {
CodePosition::CodePosition(std::string path, int line)
: d_file(filename(std::move(path)))
, d_line(line)
{}
std::string CodePosition::filename(std::string path) const
{
const size_t pos = path.find_last_of("/");
if (pos == std::string::npos || pos == path.length() - 1)
{
return path;
}
return path.substr(pos+1);
}
// FREE STREAM OPERATOR
std::ostream& operator<<(std::ostream& os, const CodePosition& v)
{
return os << "[" << v.d_file << ":" << v.d_line << "]";
}
} // close esma package namespace
int main() {
std::cout << ESMA_CODEPOS();
return 0;
}
hungarian notation
There are two version. “Systems Hungarian” is the useless version because it’s just typing the type of a variable into the name. Modern editors can show the type automatically. And there are so many types in general, it’s useless. The useful version is “Apps Hungarian”, and it’s about semantic information, that means noting safe vs unsafe strings, max
, min
values, index
of arrays, pointers to stuff, maps / sets / ranges of stuff. This post is useful. I like to have conventions like these in my interview questions.
arr_prices
or rng_prices
for arrays (or prices
is usual), map_name_2_age
for maps, idx_price
or i
or i_price
. I like also max_price
or mx_price
, cur_price
, prev_price
. I use lo
, hi
for limits, or lim_prices
, len_prices
for length. I use dif_prices
for difference, cnt_prices
or n_prices
for counters, ptr_prices
for pointers, tmp_price
for temporary price (save value to assign later). For Hungarian notation, there are popular equivelants like Sav
, Prev
, Cur
, Next
, Nil
, Min
, Max
. Also for C++, it’s useful to have g_
for global variables, m_
for class members. I also like isXXX
(like isValid
) for functions that return booleans, getXX
/ setXX
(like getPrice
) for functions that return values or set specific fields.
dangling reference problem
I had the following thing (simplified version) when doing some tests with an object that contained references (name&
in Person
) and a function creating a prepopulated dummy version of an object.
// the DANGLING REFERENCE problem
#include <string>
#include <iostream>
struct Person {
const std::string& name; // references are evil!!
// usually when this is really expensive you think of passing by reference, but now you don't have control of the lifetime!!!
int age;
};
Person makeDefaultPerson(){
const std::string& dummy_name = "Bob"; // you create dummy_name here
int dummy_age = 23;
return Person{
.name = dummy_name,
.age = dummy_age
};
// the dummy name is deleted here!! but there is still a reference to this!!
}
int main() {
const auto p = makeDefaultPerson();
std::cout << "Person has age " << p.age << std::endl;
std::cout << "Person has name " << p.name << std::endl; // you are using name that does not exist
return 0;
}
The solution to the problem would be to make copies. But sometimes this is expensive.
The most straightforward solution is using shared pointer:
// solution with shared pointer!!
#include <string>
#include <iostream>
#include <memory>
struct Person {
std::shared_ptr<std::string> name;
int age;
};
Person makeDefaultPerson(){
const std::string dummy_name = "Bob";
// auto dummy_name2 = std::make_shared<std::string>("Bob"); // could also initialise as unique pointer to string from the beginning
int dummy_age = 23;
return Person{
.name = std::make_shared<std::string>(std::move(dummy_name)), // the shared pointer of Person object will increase the reference counter of string "Bob"
// using move to avoid unnecessary copy!!
// .name = dummy_name2,
.age = dummy_age
};
// the dummy_name2 shared pointer going out of scope will decrease the shared pointer's reference counter of string "Bob"
// but the counter is still positive, string "Bob" is still referenced, hence it won't be deleted
}
int main() {
const auto p = makeDefaultPerson();
std::cout << "Person has age " << p.age << std::endl;
std::cout << "Person has name " << *p.name << std::endl;
return 0;
}
Having to initialise a variable like make_shared
is problematic. Because frequently other people wrote the code before me and later down the line I may introduce shared_ptr
.
The easy way to solve it is to do an unnecessary copy, but sometimes this is really expensive (for large objects).
The smart way to remember is moving the object (and maybe introducing some const conversion magic):
Another solution is using unique_ptr
. Imo, really unpredictable and unintuitive to use, the benefit is marginal. (it’s difficult to pass as arguments to functions, it’s difficult to transfer ownership if something is const
, bad error messages, …).
// solution with unique pointers
#include <string>
#include <iostream>
#include <memory>
#include <utility>
struct Person {
std::unique_ptr<std::string> name;
int age;
};
Person makeDefaultPerson(){
auto dummy_name = std::make_unique<std::string>("Bob"); // you have to initialise using a smart_pointer
// otherwise, you are doommed to make an unnecessary copy (or you will have to use weird `movea)
int dummy_age = 23;
// std::unique_ptr<std::string> dummy_name2 = std::move(dummy_name);
return Person{
.name = std::move(dummy_name), // the shared pointer of Person object will increase the reference counter of string "Bob"
.age = dummy_age
};
// the dummy_name shared pointer going out of scope will decrease the shared pointer's reference counter of string "Bob"
// but the counter is still positive, string "Bob" is still referenced, hence it won't be deleted
}
int main() {
const auto p = makeDefaultPerson();
std::cout << "Person has age " << p.age << std::endl;
std::cout << "Person has name " << *p.name << std::endl; // you are using name that does not exist
*p.name = "Tony";
std::cout << "Person has name " << *p.name << std::endl; // you are using name that does not exist
return 0;
}
unique pointers and transferring ownership, const pointer of T vs pointer of const T
There is a different between const pointer of T vs pointer of const T
.
We use so often const auto&
as a view of a variable in C++, but it needs to be used carefully with pointers.
This example gives the chance to showcase how awful error messages C++ has.
#include <string>
#include <iostream>
#include <memory>
#include <utility>
int main() {
const std::unique_ptr<std::string> cup_s = std::make_unique<std::string>("Ken");
std::cout << "Const unique pointer of string " << *cup_s << std::endl; // you are using name that does not exist
*cup_s = "Barbie";
std::cout << "Const unique pointer of string updated to " << *cup_s << std::endl; // you are using name that does not exist
// cup_s = std::make_unique<std::string>("John"); // ERROR: "discards qualifiers" aka CONSTNESS ERROR (you said it's const, aka it will not change, but now you try to change it)
std::unique_ptr<const std::string> up_cs = std::make_unique<const std::string>("Ken");
// std::unique_ptr<const std::string> up_cs = std::make_unique<std::string>("Ken"); // Also possible, casting from string to const string is possbile here
std::cout << "Unique pointer of const string " << *up_cs << std::endl;
// *up_cs = "Barbie"; // ERROR: "no match for 'operator='", such a bad error message :(
// it's actually a CONSTNESS ERROR
up_cs = std::make_unique<const std::string>("Mary");
std::cout << "Unique pointer of const string updated to " << *up_cs << std::endl;
auto up_cs2 = std::move(up_cs); // Transferring ownership of unique pointers
// std::cout << "Unique pointer of const string " << *up_cs << std::endl; // Segmentation Fault aka accessing wrong memory
// but nobody prohibits you from using this BROKEN unique pointer!!!
// auto up_cs3 = std::move(cup_s); // COMPLIATION ERROR: moving a const pointer fails
// moving means that you are changing the state of `cup_s`, but that violates CONSTNESS
return 0;
}
lambdas: declaring them, assigning them to variables etc.
#include <string>
#include <iostream>
#include <cassert>
#include <functional>
#include <algorithm>
int getSizeA(const std::string& s){
return s.size();
}
// you can pass lambda functions as arguments to other functions
int getSumA(int a, const std::string& s, const std::function<int(const std::string&)>& string2num){
return a + string2num(s);
}
// you can also have a default argument for the function
int getSumB(int a, const std::string& s,
const std::function<int(const std::string&)> string2num = std::function{[](const std::string& s) -> int {
return s.size();
}}
){
return a + string2num(s);
}
// in C++20 and forward, you can have auto arguments in functions!!
int main() {
std::function<int(const std::string&)> getSizeB = [](const std::string& s){
return s.size();
};
// no need to declare the input parameters twice
// you can also declare the return type with cool syntax
auto getSizeC = [](const std::string& s) -> int {
return s.size();
};
assert((4 == getSumA(1, "hey", getSizeC)));
assert((4 == getSumB(1, "hey"))); // using the function with default lambda
// using a different string2num conversion
auto getAs = std::function{[](const std::string& s)-> int {
return std::count_if( s.begin(), s.end(), []( char c ){return c =='A';});
}};
assert((10 == getSumB(7, "BANANA", getAs)));
// this shows the manual labour you need to do in C++
// although versatile, it is not expressive out of the box and not concise when writing code
// More notes:
// * lambdas are anonymous, you need to assign them to a variable to reference them
// * you can pass scope variables inside the `[]`, that can be accessed in the body of the function
// * std::function cannot take defaults. It would be complicated to implement, without particular benefit. default parameters don't make much sense for lambdas,
// as they are short lived, and used for particular reasons.
// * however, for a lambda argument, you can also define a default lambda (see above)
return 0;
}
c++ naming convention with auto
In C++17 and above, it’s equivelantly efficient to write the following (link):
Dog obj1{args...};
auto obj2 = Dog{args...};
auto obj2 = Dog(args...); // could also do this
// also
auto obj3 = std::make_unique<Dog>(args...);
Using auto always is generally more consistent. You can avoid problems with weird constructor sytnax. You should use intellisense to view the types of variables.
c++: returing references
You can write functions that return references. Remember to pass relevant input as simple reference (not const ref) and to use auto&
when calling the function.
In general, const ref
variables are useful when just viewing an object and simple ref
functions are used when we need to update member data of the variable.
Note that in the functional programming paradigm, we never update an existing object. We alwyas copy, update a field and then reassign. Functions never have side effects to inputs parameters. In the contrary, when passing by
ref
(and notconst ref
) in C++, the user can change whatever he wants, and you just trust that he operates in the correct manner (not corrupting your data, etc).
// copy to https://godbolt.org/
#include <string>
#include <iostream>
#include <cassert>
struct Person {
std::string name;
int age;
};
struct Deal {
Person buyer;
Person seller;
int amount;
};
Person getBuyer(const Deal& deal){
return deal.buyer;
}
Person& getBuyerRef(Deal& deal){
return deal.buyer;
}
int main() {
// useful way initializing simple structs
auto p1 = Person{
.name = "Mike",
.age = 22
};
auto dealA = Deal{
// .buyer = Person{
.buyer = {
.name = "Mike",
.age = 22
},
.seller = {
.name = "Bob",
.age = 25
},
.amount = 43
};
auto& buyerA = getBuyerRef(dealA);
// checking references actually are referring to same objects (not just equality)
assert((&buyerA == &dealA.buyer)); // same Buyer object
// see this in action
assert((buyerA.name == dealA.buyer.name));
buyerA.name = "Mary";
assert((buyerA.name == dealA.buyer.name)); // both variables changed name
const auto buyerB = getBuyer(dealA);
assert((&buyerB != &dealA.buyer)); // different objects
return 0;
}
python acronyms
EAFP: Easier to ask for forgiveness than permission. This coding style is assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false. LBYL: Look before you leap. It’s the opposite of the previous coding style. This coding style explicitly tests for pre-conditions before making calls or lookups. REPL: Read-eval-print loop. It’s another name for the interactive interpreter shell. Replit and other tools take the name from it. Originally developed for the LISP language (the mother of functional programming) according to wiki.
VScode like a pro
Firstly, with CMD + SHIFT + P
I can bring the Command Palette and search for all utilities. This is the smartest thing in VSCode.
In Command Palette type Shortcuts
to view the provided shortcuts.
You can also view a few cheat sheets before reading below for my personal favourties.
- I often want to see the implementation of the function i view. In Command Palette type “Go To Implementation” (shortcut:
CMD + Click
) - I often want to see where the function is called. In Command Palette type “Find All References” (shortcut:
CMD + SHIFT + F12
orCMD + F12
).