Why C++:
- Modern, but old language
- Strongly typed, safe, high-performance with zero-cost abstractions.
- Portability to WASM, Python Modules.
Useful stuff:
- C++ reference: C++ Bible
- Compiler support: allows you to see which feature from each standard is implemented in which compiler. A godsend!
- Specifiers: Modify the function's properties
noexcept: The function doesn't lauch exception. If it does, shit goes down.[[nodiscard]]: You have to assign to something whatever the function returns.inline: Signals the compiler that you prefer to not deal with the overhead of a function call. Don't use with recursive functions.constexpr: The function results can be evaluated at compile time. Use with simple functions, without try-catch blocks.
- Parameter passing convention: Types of parameter passing.
- By value -
type p: It makes a copy. - By constant value -
type const p: It makes a copy, but it can't be modified. - By reference -
type & p: Passes a reference to the object. - By constant reference -
type const & p: Passes a reference to the object, but it can't be modified. - By movement -
type && p: Passes the ownership of the object to the function.
- By value -
- Constructor: Get executed when the object is created. Methods, same name as the class.
- Called with its arguments with
class{ p1, p2, ...}. - Initialization list
class(...) : p1{...}, p2{...}, ...: List of constructors to be run before this constructor. Add here the constructors of the class members.
IMPORTANT: Class members don't initialize in the order that they appear, they are initialized in the order they are declared in.
- Called with its arguments with
- Accesibility:
public: Everyone can access.- Default in
struct.
- Default in
privateOnly members can access.- Default in
class.
- Default in
protected: Only members and children can access.
- Specifiers:
final: To prevent further inheritance.
- Specifiers: Modify the function's properties
static: Allow for it to be called outside the class.const(after parenthesis): Use it if you don't modify any class member.friend: As if you define the function outside the class, you must pass a parameter for the class object. It has acces to the private members of the class.
- Use
enum class.
Don't use it, use aliases: using new_type = old_type.
Don't use it, use constexpr.
This is C++'s implementation of generic programming, which allows you to use the same function for different parameter types/classes. This is not overloading, because that requires two function definitions.
You declare a template through template <NAME>, and that NAME will represent any type defined in the template. A template only applies to the next function definition.
In order to constraint the number of types, you can use concepts, which are specified before the template type name. If no concept is specified, all types will be valid.
C++20 comes with some predefined concepts in the <concepts> header.
E.g. the following function allows checking if any integer number is even, allowing for all integer types (int, long, etc.).
#include <concepts>
template <std::integral INT_T>
bool is_even(INT_T n) {
return n % 2 == 0;
}
int two = 2;
long two_long = 2;
is_even(two);
is_even(two_long);std::array<type, size>(<array>): Classic arrays, but betterstd::vector<type>(<vector>): A non-fixed size array- Constructor:
{ value0, ... } - To append a value, use
.push_back(value)
- Constructor:
std::map<keyType, valueType>(<map>): Ordered key-value pairs. Keys must be constant.- Constructor:
{ {key0, value0}, ... } []operator will create the element if it doesn't exist. Use.at()to access the element..contains(key)checks if a key exists in the map
- Constructor:
std::unordered_map<keyType, valueType>(<unordered_map>): Unorderedstd::map.std::set<type>(<set>): A collection of homogeneous values..insert(element)inserts an element..contains(element)checks if an element is inside the set..extract(element)deletes an element and returns it.
Most of these structures share the following methods:
.clear()clears the structure..size()returns the number of elements.
std::tuple<type0, ...>(<tuple>): A fixed-size collection of heterogeneous (multiple type) values.- Use
std::make_tuple(value0, ...), or{value0, ...}to create one. - Extremely useful to return several data from a function, as it can be unpacked:
std::tuple<int, std::string> foo() { return {69, "nice"}; } [n, msg] = foo();
- Use
std::initializer_list<type>(<initializer_list>): Allows for a cleaner syntax for class constructors, with arbitrarily sized lists.
E.g.:class Add { public: Add(std::initializer_list<int> l): count{}, numbers {l} { count = 0; for (auto & i : l) { count += i; } } int get() { return count; } private: int count; std::vector<int> numbers; }; Add a {1, 2, 3, 4}; a.get(); // 10
std::function<rtype(arg0_type, ...)>(<functional>): Wrapper for a function that can be passed as an argument. If it's a member function, the first argument must be the object:std::function<rtype(Obj &, arg0_type, ...)>, and it must be called with.
E.g.:class Foo { Foo(int num) : num_(num) {} void print_add(int i) const { std::cout << num_ + i << '\n'; } int num_; }; std::function<void(const Foo&, int)> f_add_display = &Foo::print_add; const Foo foo(69); f_add_display(foo, 1);
for (type elem : iterable) {}
- Remember you can use
autofor the type (you still need to put yourconstand&if needed). - You can use structured bindings in order to make your life easier:
for (type [memberA, memberB, ...] : iterable) {}.
Specially useful with maps. E.g.:std::unordered_map<std::string, std::array<int>> my_map { {"A", [0, 1, 2]}, {"B", [3, 4, 5]} }; for (const auto & [key, value] : my_map) { std::cout << key << ": " << value << std::endl; }
Nameless functions: [captured_variable0, ...] (param_type param0, ...) -> return_type { <body> }
- The return type is optional
- Captured variables are variables from the local scope to be passed to the lambda function
- They are copied by default, use
&captured_variableto reference it. - Use
[&]to capture everything,[=]to copy everything.
- They are copied by default, use
E.g.:
int x = 69;
[&x] (int y) -> std::string { return std::to_string(y + x); }- Useful to map keys to functions:
int x = 69; std::string add_stuff(int y) { return std::to_string(y + x); } using myFunction = std::function<std::string (int)>; const std::unordered_map<std::string, myFunction> myMap { { "+", [&x] (int y) {return add_stuff(y);} } }; int y = 420; myMap["+"](y)
Custom C++ Exceptions for Beginners
You can have header-only libraries, by simply having functions defined there, but it greatly increases compilation times and forces to recompile every time the code changes without the interface changing.
- Declare functions in header file (
.hpp), and define them in the source files (.cpp).
If you want a function to be defined only in the header, it has to beinline. You can also have source file ("private") inline functions. - For non-constant variables, declare them using
externin the header and assign their values in the source file. - Classes and its constructors are defined in the header, but the methods are only declared and must be defined in the source file, adding the class namespace (
type MyClass::my_method() {...}). Attributes can be either declared or defined in the header. - Macros have to be defined in the header.
For any type of library, use guards on the header files (#ifndef LIB_HPP, #define LIB_HPP) to prevent double declaration.
E.g:
// lib.hpp
#ifndef LIB_HPP
#define LIB_HPP
inline say_hello() { std::cout << "hello\n"; }
int foo(int bar);
const baz = 69;
extern int myVar;
class MyClass() {
public:
MyClass(float y): _y {y} { }
int get();
private:
int _x = 0;
float _y;
};
#endif// lib.cpp
int foo(int bar) {
return bar + myVar;
}
int myVar = 0;
MyClass::get() { return _x + _y; }Couldn't get them to work, support is still shit.