.cpp are meant to be compiled.hpp are meant to be included in other source files
Each .cpp file is a compilation unit. The compiler will :
This object format contains:
A static library (generally .a or .lib) is more or less a bunch of object files put together.
A dynamically linked library has 3 parts:
Header files contain function prototypes for compiling.
Your compiler needs to be able to find the header files when compiling so it knows what the functions and structures are. You can either configure your compiler to search for headers in an additional directory, or put the header files in with the rest of header files that your compiler comes with. Else the compiler complains that it can’t find X.h files.
After your compiler compiles all your source files it has to link them together. In order for the program to link properly, it needs to know the addresses of all your functions. For a dll, these addresses are in the library file. The library file has the Import Address Table so your program can import the functions at runtime. You can either configure your compiler to search for library files in an additional directory, or put the library files in with the rest of the compiler’s library files. You also have to tell the linker to link against the library file in the linker, else the linker complains that it can’t find -lX or X.lib. If the linker complains about an undefined reference, it probably means it was never told to link the library.
After your program is compiled and linked, you need to be able to link against the library when you run it. In order to run a dynamically linked application, you need to be able to import the library binaries at runtime. Your OS needs to be able to find the library binary when you run your program. You can either put the library binaries in the same directory as your executable, or a directory that your OS keeps library binary files.
Dynamic libraries declare a list of exported (and imported) symbols. At runtime, there are system calls that allow to get the addresses of those symbols and use them almost normally. For dlls, the code of the referenced function is not included in the exe, but just a reference to the function inside a dll is included. All the used symbols from a dll are loaded either at load time directly by the loader (on Unix like systems) or with import libraries on Windows. The OS contains a loader aka dynamic linker that resolves these references and loads dlls if they are not already in memory at the time a program is started.
int x; // declaration, default init
x = 5; // copy assignment
int a = 1; // copy init
int b(2); // direct init (avoid)
int c {3}; // brace/uniform/list init (preferred)
int d = {4}; // copy brace init
int e {}; // value init (if value will be replaced)Brace initialization is simpler, more general, less ambiguous, and
safer than for other forms of initialization. It disallows narrowing
conversions (e.g. can’t assign float to int). Use = only
when you are sure that there can be no narrowing conversions. For
built-in arithmetic types, use = only with
auto. Avoid () initialization, which allows
parsing ambiguities. Use ={...} if you really want an
initializer_list<T>.
Lifetimes:
Lifetime is a runtime property and is usually tied to scope (a compile time property). Objects must be created and initialized no later than the point of definition, and destroyed no earlier than the end of the set of the curly braces in which they are defined (or, for function parameters, at the end of the function).
Temporaries, Moves, and Copies: Name Counting
// Two Names: It’s a Copy
std::vector<int> foo;
FillAVectorOfIntsByOutputParameterSoNobodyThinksAboutCopies(&foo);
std::vector<int> bar = foo; // Yep, this is a copy.
std::map<int, string> my_map;
string forty_two = "42";
my_map[5] = forty_two; // Also a copy: my_map[5] counts as a name.
// One Name: It’s a Move
std::vector<int> GetSomeInts() {
std::vector<int> ret = {1, 2, 3, 4};
return ret;
}
// Just a move: either "ret" or "foo" has the data, but never both at once.
std::vector<int> foo = GetSomeInts();
// or
std::vector<int> foo = GetSomeInts();
// Not a copy, move allows the compiler to treat foo as a
// temporary, so this is invoking the move constructor for
// std::vector<int>.
// Note that it isn’t the call to std::move that does the moving,
// it’s the constructor. The call to std::move just allows foo to
// be treated as a temporary (rather than as an object with a name).
// it’s just a cast to an rvalue-reference. It’s only the use of that reference by a move constructor or move assignment that does the work.
std::vector<int> bar = std::move(foo);
// Zero Names: It’s a Temporary
// if you want to avoid copies, avoid providing names to variables.
void OperatesOnVector(const std::vector<int>& v);
// No copies: the values in the vector returned by GetSomeInts()
// will be moved (O(1)) into the temporary constructed between these
// calls and passed by reference into OperatesOnVector().
OperatesOnVector(GetSomeInts());r-value (or contents) and the l-value (or location) of a variable. left and right-hand side of an assignment statement. An l-value refers to an object that persists beyond a single expression. An r-value is a temporary value that does not persist beyond the expression that uses it.
boolsigned charshort intintlong int, longlong long int, long longfloatdoublelong doublestd::nullptr_t (C++11)Long and long int are at least 32 bits. long long and long long int are at least 64 bits
std::int8_tstd::int16_tstd::int32_tstd::int64_tstd::size_t: can hold all possible object sizes, used
for indexing, unsignedconst float pi = 3.14159265f; // need the f suffixstring s = "string"; // is this UTF-8?
#include <limits>
int imin = numeric_limits<int>::min();
int imax = numeric_limits<int>::max();(int)xstatic_cast<int>(x): where you basically want to
reverse an implicit conversion, with a few restrictions and additions.
Performs no runtime checks: should be used if you know the specific
type. There is a valid conversion in the language, or an appropriate
constructor that makes it possible. Can be a bit risky when you cast
down to an inherited classreinterpret_cast<int>(x): always dangerous. You
tell the compiler: “trust me”const_cast<int>(x)dynamic_cast<int>(x): when you don’t know what
the dynamic type of the object is. It returns a null pointer if the
object referred to doesn’t contain the type.https://stackoverflow.com/questions/103512/why-use-static-castintx-instead-of-intx
Note: Guarantees zero implicit conversions/temporaries, zero narrowing conversions, and zero uninitialized variables!
auto x = 1; // int
auto x = 1.f; // float
auto x = 1ul; // unsigned long
auto x = 1LL; // long long
auto x = "1"s; // string
// to track, deduce
auto var = init;
auto f() { ... }
// to stick, commit
auto var = type{init};
auto f() -> type;
// or
type var{init};
type f();Don’t use auto if explicit type{} and
non-(cheaply-)moveable type.
auto lock = lock_guard<mutex>{ m }; // error, not movable
auto ai = atomic<int>{}; // error, not movable
auto a = array<int,50>{}; // compiles, but needlessly expensiveAlways prefer enum classes to the usual C-like enums.
enum class Direction : char { Up, Down };
auto d2 = Direction::Up;for (int i = 0; i < v.size(); i++) {
cout << v[i] << "\n";
}for (auto& x : v) {
cout << x << "\n";
}int a = 5;
auto x = [a] (int b) { return a + b; };
// └┬┘ └──┬──┘
// │ └── Parameter list
// └── Capture listUse using: this can be used in function scope:
e.g. using namespace fs = std::filesystem; Avoid
using namespace std since std is big and might
alias something there.
using MyInt = int; // preferred from C++11 onwards
typedef int MyInt;Not null-terminated.
string a = "a";
string abc = a + "bc";
int n = abc.length();
abc[1] // "b"
abc.substr(startIndex, length);
auto x = std::to_string(42)Most are not thread-safe.
Built-in arrays:
int a[] = {1,2,3,4};
int len = 4;
// or
len = sizeof(a) / sizeof(a[0])
int zeros[9][9] = {0}Need to keep info of the array size separately. Always passed by ref to functions.
Container array:
#include <array>
array<int,4> a = {1,2,3,4};
int len = a.size();vector<int> v1;
v1.push_back(1); // [1]
v[0] // 1
v.at(0) // 1, does a range-check
vector<int> v2 = {1,2,3,4};
vector<int> v3 {1,2,3,4};
v1.size() // 4
v2.back() // 4
v2.pop_back(); // void
v2.back() // 3
v2.emplace_back(1); // AVOID, unless significant perf increases
vector<int> v(10); // size 10, initial values 0
vector<int> v(10, 1); // size 10, initial values 1
// creates a sub-vector from v
vector<int> subvec(v.begin() + 4, v.end() - 2);
// create a vector of vectors using the fill ctor
vector<vector<int>> mat(R, vector<int>(C));
v1.swap(v2); // swaps contents of vectors in O(1) time
// setting capacity
vector<T> result;
result.reserve(size);
vector<T> result(size); // will call default ctor (if it exists)// why one for pair, why not just use tuple?
#include <utility>
pair<int,int> p = {1,2};
#include <tuple>
// can tuples be any size?
// can tuples elements be named?
tuple<int,int,int> t = {1,2,3};
tuple<int,char> foo (10, 'x');
get<1>(foo) = 'y';
auto t = std::make_tuple(1, "Foo", 3.14);
std::get<0>(t) // index
std::get<int>(t) // type-based (>= C++14)
std::tie(x,y) = get_point(); // tuple deconstructionstruct P {
int x, y;
// custom comparison operator
// needed for sorting
bool operator<(const P &p) {
if (x != p.x) return x < p.x;
else return y < p.y;
}
}set: ordered, implemented using balanced binary tree
O(log n)unordered_set: hashset O(1)multiset: can contain multiple instances of an
elementunordered_multiset: can contain multiple instances of
an elementset<int> s = {1,2,3};
s.insert(3);
s.count(3) // always 1 or 0, should've been "contains"!
s.erase(2)
s.size() // 2
multiset<int> m = {3,3,3};
m.count(3); // 3
m.erase(m.find(3)); // count is 2
m.erase(2); // count is 0map: balanced binary treeunordered_map: hashmapmap<string,int> m;
m["a"] = 65;
cout << m["b"]; // adds {b:0} to map
m.count("b"); // exists
for (auto x : m) { /*do stuff with*/ x.first, x.second}bitset<4> a;
a[1] = 1;
a[3] = 1; // 1010
// or
bitset<4> b(string("1010")); // from right to left
b.count(); // 2
a&b, a|b, a^bOperations are a bit slower than for a vector.
deque<int> d = {2};
d.push_back(3); // [2,3]
d.push_front(1); // [1,2,3]
d.pop_back(); // [1,2]
d.pop_front(); // [2]A container adapter usually based on deque.
stack<int> s;
s.push(3);
int t = s.top();
s.pop(); // void return
s.size();
s.empty();Might be preferable to use vector instead:
stack<int> q;
q.push(3);
q.front(); // 3
q.pop();Implemented using a heap.
priority_queue<int> q;
q.push(1);
q.push(3);
q.push(2);
q.top(); // 3
q.pop();https://pvs-studio.com/en/blog/posts/0989/
#include <algorithm>
max(a,b), min(a,b)
// for iterators, returns iterators
auto max = *max_element(it.begin(), it.end());
auto max = *min_element(it.begin(), it.end());
auto [min_it, max_it] = minmax_element(begin(v), end(v));
// numeric
// fills the range with sequentially increasing values
iota(it.first(), it.last(), startValue);
find(begin(v), end(v), val);
auto is_even = [](int i){ return i%2 == 0; };
find_if(begin(v), end(v), is_even);
// binary_search is useless: returns a bool
bool found = binary_search(v.begin(), v.end(), val);
Iter i = lower_bound(v.begin(), v.end(), val); // does a binary search
sort(v.begin(), v.end());
stable_sort(v.begin(), v.end());
std::sort(std::execution::par, v.begin(), v.end()); // parallel sorting execution_policy
// std::transform_reduce
// seq and subseq need to be ordered
// subseq need not be contiguous in seq
std::includes(seq.begin(), seq.end(), subseq.begin(), subseq.end());for (int i = 0; i < n; i++) {
b[i] = a[i] + 1.0;
}
std::transform(a.begin(), a.end(), b.begin(), [] (double c) -> double { return c + 1.0; });
for (int i = 0; i < n; i++) {
b[i] = a[i] + 1.0;
}
// reduce(begin, end, start, reducer)
sum = std::reduce(a.begin(), a.end(), static_cast<double>(0.0), std::plus<double>());
std::ranges:count_if(xs, f)auto const nums = std::array{0, 0, 1, 1, 2, 2};
auto const animals = std::array{"cat"s, "dog"s};
iota(0, 5) | chunk(2) // [[0, 1], [2, 3], [4]]
nums | chunk_by(std::equal_to{}) // [[0, 0], [1, 1], [2, 2]]
iota(0, 5) | slide(3) // [[0, 1, 2], [1, 2, 3], [2, 3, 4]]
iota(0, 10) | stride(3) // [0, 3, 6, 9]
repeat(42) | take(5) // [42, 42, 42, 42, 42]
zip_transform(std::plus{}, nums, nums) // [0, 0, 2, 2, 4, 4]
zip(iota(0, 3), iota(1, 4)) // [(0, 1), (1, 2), (2, 3)]
iota(0, 4) | adjacent<2> // [(0, 1), (1, 2), (2, 3)]
iota(0, 4) | pairwise // [(0, 1), (1, 2), (2, 3)]
iota(0, 4) | adjacent_transform<2>(std::plus{}) // [1, 3, 5]
iota(0, 4) | pairwise_transform(std::plus{}) // [1, 3, 5]
animals | join_with(',') // ['c', 'a', 't', ',', 'd', 'o', 'g']
cartesian_product(iota(0, 2), "AZ"s) // [(0, 'A'), (0, 'Z'), (1, 'A'), (1, 'Z')]
"APL"s | enumerate // [(0, 'A'), (1, 'P'), (2, 'L')C++ has the notion of a reference.
int x = 4;
// reference
void func1(int& val) {
// you only know the value
val = 5;
}
func1(x);
// pointer
void func2(int* val) {
// you know the address and value
*val = 5;
}
func2(&x);
// both func1 and func2 change the value of xUse pointers if you want to:
Use references otherwise.
https://stackoverflow.com/questions/114180/pointer-vs-reference https://stackoverflow.com/questions/5816719/difference-between-function-arguments-declared-with-and-in-c
C++11 introduces move semantics. The STL includes the concept of “smart pointers”, each with different substructural logic:
std::unique_ptr: linear (1-use) self owning pointer
(1u1o) make_unique<T>()std::shared_ptr: normal (N-use) self owning pointer
(Nu1o) make_shared<T>()std::weak_ptr: normal (N-use) non-owned pointer
(Nu0o)T *&&: affine (0/1-use) non-owned pointer
(?u0o)TODO:
When passing args to a function f:
f(type arg) value: arg isn’t modified and arg is of
primitive type or is an rvalue, or if f would copy the arg even if
passed by reff(type& arg) reference: if f intends to modify the
argf(const type& arg) const reference: arg isn’t
modified and arg isn’t of primitive type|——————|——————|——————————-|—————–| | out | X f() |
X f() | f(X&) | | inout |
f(X&) | f(X&) | f(X&)
| | in | f(X) | f(const X&) |
f(const X&) | | in & retain copy |
f(X) | f(const X&);
f(X&&) move | | | in & move from |
f(X&&) | f(X&&) |
f(X&&) |
Constructors are the primary example of “in & retain copy”.
Primitive types are built-in types, iterators and function objects. Pass by value copies unless the object is an rvalue, in which case it is moved. This applies even when using smart pointers.
Raw pointers and references are the preferred parameter type (unless
you wish to transfer ownership). Never pass smart pointers (byval or
byref) unless you want to manipulate the pointer (store, change, let go
of ref). Express ownership using unique_ptr wherever
possible (incl when you don’t know if object will actually be shared).
Else use make_shared, if object will be shared.
void f(T& t) { }
auto up_t = make_unique<T>();
f(*up_t);
void g(T* t) { }
auto sp_t = make_shared<T>();
g(sp_t.get());class employee {
std::string name_;
std::string city_;
public:
// in & retain copy:
void employee( std::string name, std::string city )
: name_{std::move(name)}, city_{std::move(city)} { }
};
// Default param passing:
// Always one copy assignment; may alloc if string is large
void set_name( const std::string& name ) {
name_ = name;
}
// If optimization is justified, add this overload:
// Steals from rvalues; one copy assignment if named object
void set_name( std::string&& name ) noexcept {
name_ = std::move(name);
}Structs have default public members and bases Classes have default private members and bases
Both classes and structs can have a mixture of public, protected and private members, can use inheritance and can have member functions.
It is recommended that structs are used as plain-old-data (POD) structures without any class-like features, and that classes as used as aggregate data structures with private data and member functions.
struct film {
string title;
int year;
};
film x;
x.title = "x";
x.year = 2000;class Box {
public:
// Default constructor. The compiler doesn't provide one.
Box() {}
// Initialize a Box with equal dimensions (i.e. a cube)
// `explicit` else `Box b = 42` is valid.
explicit Box(int i)
: width_(i), length_(i), height_(i) // member init list
{}
// Initialize a Box with custom dimensions
Box(int width, int length, int height)
: width_(width), length_(length), height_(height)
{}
int volume() { return width_ * length_ * height_; }
private:
// Will have value of 0 when default constructor is called.
// Else would be garbage values.
int width_{ 0 };
int length_{ 0 };
int height_{ 0 };
};
int main()
{
Box b0; // Box()
// Using uniform/brace initialization (preferred):
Box b1 {5}; // Box(int)
Box b2 = {5}; // Box(int)
Box b3 {5, 8, 12}; // Box(int, int, int)
// Using function-style notation:
Box b4(2, 4, 6); // Calls Box(int, int, int)
}class A {};
Implicit member functions (compiler-provided):
Example usage:
#include <iostream>
// Empty struct. No function explicitly defined.
struct Elem { };
// Factory method that returns an rvalue of type Elem.
Elem make_elem() { return Elem(); }
int main() {
// move constructor
Elem e1 = make_elem();
// copy assignment
Elem e2;
e2 = e1;
// copy constructor
Elem e3 = e1;
// move assignment
e2 = make_elem();
// address operator
std::cout << "e2 is located at " << &e2 << std::endl;
return 0;
}class Foo
{
void doSomething() {
std::cout << this.mValue << '\n';
}
// C++23 deducing this: now can see value category of self
void doSomething2(this Foo const& self) {
std::cout << self.mValue << '\n';
}A friend class in C++ can access the private and protected members of the class in which it is declared as a friend.
Like generics, but more general. When a function or class is instantiated from a template, a specialization of that template is generated by the compiler for the set of arguments used.
Function templates
// Equivalent (typename is newer):
template<class T> declaration;
template<typename T> declaration;
template<typename T>
T max(T a, T b) {
return a > b ? a : b;
}
auto x = max(3.0, 7.0);
auto y = max<double>(3, 7.0);Class templates
// N is a non-type parameter.
template <typename T1, typename T2, int N>
class pair {
T1 values [N];
T2 other;
...
};See Eigen.
Branched:
enum class Side { Buy, Sell };
void RunStrategy(Side side) {
const float price = Price(side, value, credit);
...
}
float Price(Side side, float value, float credit) {
return side == Side::Buy ? value - credit : value + credit;
}Templated:
template<Side T>
void Strategy<T>::RunStrategy() {
const float price = Price(value, credit);
...
}
template<>
float Strategy<Side::Buy>::Price(float value, float credit) {
return value - credit;
}
template<>
float Strategy<Side::Sell>::Price(float value, float credit) {
return value + credit;
}coroutineA coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack. This allows for sequential code that executes asynchronously (e.g. to handle non-blocking I/O without explicit callbacks), and also supports algorithms on lazy-computed infinite sequences and other uses.
Has one of the following keywords in it:
co_await is used with another “awaitable” object (like
a coroutine, but there are non-coroutine awaitables), and will suspend
the coroutine if the awaitable says to do so.co_yield is for the ‘generator’ pattern, which is a
function that is called and runs until it’s able to yield a value,
suspending the function and providing that value to the caller (when
resumed, it will run until the next yield (or return)).co_return is the coroutine version of
return (the distinction is needed because sometimes a
coroutine just runs until completion, and you need a way to say this
function that returns is a coroutine, not a normal function).iostream#include <iostream>
cin >> x >> y; // Read words x and y (any type) from stdin
cout << "x=" << 3 << endl; // Write line to stdout. Prefer '\n'
cerr << x << y << flush; // Write to stderr and flush
c = cin.get(); // c = getchar();
cin.get(c); // Read char
cin.getline(s, n, '\n'); // Read line into char s[n] to '\n' (default)
if (cin) // Good state (not EOF)?
// To read/write any type T:fstream#include <fstream>
ifstream fi("file"); // Open text file for reading
if (fi) // Test if open and input available
fi >> x; // Read word from file
fi.get(s); // Read char or line
fi.getline(s, n); // Read line into string s[n]
ofstream fo("file"); // Open file for writing
if (fo) fo << x; // Write to file#include <cstdlib>
// Pseudo-random integral value between 0 and RAND_MAX.
auto rnd = std::rand()/(float)RAND_MAX;Calling conventions used by functions:
_cdecl is the default calling convention for C and C++.
Here the memory allocated on stack due to the function is cleared by the
caller program._stdcall is used by WIN32 API functions. Here the stack
is cleared by the callee itself. To make that safe, the exported name
contains the number of bytes that the callee will remove from the stack
(e.g. func@8).Naming conventions
extern "C" is the C naming convention: disables name
mangling (i.e. type-safe linkage; including type info in the symbol
name).Note
__cdecl before the function: follows the “C”
calling convention but C++ naming conventionextern "C" as well as __cdecl:
function uses “C” calling convention as well as the “C” naming
convention