C++: Difference between revisions
(150 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
__FORCETOC__ | __FORCETOC__ | ||
C++ is a very popular and powerful language which includes all the low-level features of [[C_(programming_language) | C]] (e.g. pointers, operator overloading) along many high-level features (RAII, STD algorithms, STL containers) thanks to the C++ standard library. | |||
==Usage== | ==Usage== | ||
Line 5: | Line 6: | ||
===Compilation=== | ===Compilation=== | ||
{{See also|CMake|Makefile}} | {{See also|CMake|Makefile}} | ||
====cmake==== | |||
====g++==== | ====g++==== | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
g++ my_driver.c [-Iincludefolder] -o my_program.out | g++ my_driver.c [-Iincludefolder] -o my_program.out | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Standard optimizations | |||
* <code>-std=c++17</code> for C++17 support | * <code>-std=c++17</code> for C++17 support | ||
* <code>-O3</code> for level 3 | * <code>-O3</code> for level 3 optimizations | ||
* <code>-g</code> to include debugging info | |||
* <code>-march=native</code> - use all instructions available on the current CPU | |||
* <code>-mtune=native</code> - optimize for the current CPU | |||
===Syntax=== | ===Syntax=== | ||
====Main==== | |||
All C++ programs launch in a <code>main</code> function. | |||
Similar to [[C (programming language) | C]], the arguments are <code>int argc</code> and <code>char *argv[]</code>.<br> | |||
These can be easily converted to a <code>std::vector<std::string></code> for convenience. | |||
<syntaxhighlight lang="cpp"> | |||
#include <string> | |||
#include <vector> | |||
int main(int argc, char *argv[]) { | |||
std::vector<std::string> args(argv, argv + argc); | |||
// Your code here | |||
return EXIT_SUCCESS; | |||
} | |||
</syntaxhighlight> | |||
====Headers==== | |||
[https://stackoverflow.com/questions/10694255/cmath-vs-math-h-and-similar-c-prefixed-vs-h-extension-headers Reference] | |||
C++ includes C-headers such as <code>math.h</code> and <code>cmath</code>.<br> | |||
The C-style header will place everything in the global namespace while the C++ header will place everything in <code>std</code>.<br> | |||
You should use <code>cmath</code>. | |||
====Lambda Expressions==== | ====Lambda Expressions==== | ||
[https://en.cppreference.com/w/cpp/language/lambda Reference] | [https://en.cppreference.com/w/cpp/language/lambda Reference] | ||
====Casting==== | ====Casting==== | ||
[https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used Types of casts] | [https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used Types of casts] | ||
C++ has several types of casts including: | |||
<code> | * [https://en.cppreference.com/w/cpp/language/static_cast <code>static_cast</code>] - your standard cast with conversion. Does not perform any checks. | ||
* [https://en.cppreference.com/w/cpp/language/dynamic_cast <code>dynamic_cast</code>] - for casting objects with checking, requires a polymorphic base class (with a virtual function). Will return nullptr. | |||
* [https://en.cppreference.com/w/cpp/language/reinterpret_cast <code>reinterpret_cast</code>] - cast without any conversion, for directly dealing with binary data, equivalent to <code>*(T*)</code> in C. | |||
====References==== | |||
References are accepted or store using <code>&</code>.<br> | |||
For example: | |||
<syntaxhighlight lang="c++"> | |||
<syntaxhighlight lang=" | void healPerson(Person &person) { | ||
person.health = 100; | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
References are like pointers since they do not copy the object except they cannot be null and they cannot be reassigned.<br> | |||
Note that primitives can also be used with references, in which case changes will propagate to the underlying value.<br> | |||
You can also use them as class attributes, initializing them in the constructor's initializer list.<br> | |||
To store references in a vector, you can use <code>std::reference_wrapper</code> and include the <code>functional</code> header. | |||
====Types==== | |||
For simple programs, you can use the standard types: | |||
* <code>int</code>, <code>uint</code>, <code>long</code>, <code>size_t</code> | |||
* <code>float</code>, <code>double</code> | |||
See [https://stackoverflow.com/questions/6462439/whats-the-difference-between-long-long-and-long SO] for the standard and guaranteed precision of these built-in types. | |||
C++ also has fixed-width types in <code>#include <cstdint</code> (since C++11).<br> | |||
[https://en.cppreference.com/w/cpp/header/cstdint cppreference cstdint]<br> | |||
I recommend using these for anything with specific or high precision requirements.<br> | |||
Typically, I use: | |||
* <code>uint8_t</code> instead of <code>char</code> or <code>std::byte</code>.<br> | |||
* <code>int64_t</code> instead of <code>long long</code> | |||
===String=== | ===String=== | ||
<code>#include <string></code><br> | <code>#include <string></code><br> | ||
If you don't need to own the string, prefer to use <code>string_view</code>. | |||
<syntaxhighlight lang="cpp"> | |||
// c-str to string | |||
char *old_string = "my c-style string"; | |||
string cpp_string(old_string); | |||
// string to c-str | |||
cpp_string.c_str(); | |||
// char to string | |||
char my_char = 'a'; | |||
string my_str(1, my_char); | |||
</syntaxhighlight> | |||
====String Interpolation==== | ====String Interpolation==== | ||
[https://stackoverflow.com/questions/10410023/string-format-alternative-in-c Reference] | [https://stackoverflow.com/questions/10410023/string-format-alternative-in-c Reference] | ||
Line 58: | Line 115: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
====Buildings Strings==== | |||
[https://www.fluentcpp.com/2017/12/19/build-strings-from-plain-string-up-to-boost-karma/ The Complete Guide to Building Strings In C++]<br> | |||
There are multiple ways of buildings strings in C++.<br> | |||
Strings are mutable in C++.<br> | |||
I typically use <code>+</code> or <code>ostringstream</code> to build strings. | |||
====std::basic_string_view==== | |||
[https://en.cppreference.com/w/cpp/string/basic_string_view std::basic_string_view] | |||
This is useful for writing functions which accept anything that looks like a string such as substrings, since typically <code>std::string::substr</code> performs a copy. | |||
Note that <code>std::string_view</code> is <code>std::basic_string_view<char></code>. | |||
===Filesystem=== | ===Filesystem=== | ||
<code>#include <filesystem></code><br> | |||
Convenient functions for filesystem. Added since C++17. | |||
====Path==== | |||
[https://en.cppreference.com/w/cpp/filesystem/path cppreference] | |||
Note if you use g++ <= version 9, you will need to add the flag <code>-lstdc++fs</code>. | |||
<syntaxhighlight lang="cpp"> | |||
using std::filesystem::path; | |||
// Initialization | |||
path my_path = "my_dir/my_file"; | |||
// or my_path = path("my_dir") / "my_file"; | |||
// Append to path | |||
path("foo") / "bar"; // path("foo/bar") | |||
path("foo") / "/bar"; // path("/bar") | |||
// Print | |||
std::cout << my_path << std::endl; // prints "my_dir/my_file" with quotes | |||
std::cout << my_path.string() << std::endl; // prints my_dir/my_file without quotes | |||
</syntaxhighlight> | |||
;Notes | |||
* <code>path</code> supports implicit conversion to <code>string</code> | |||
====Directories==== | |||
* [https://en.cppreference.com/w/cpp/filesystem/create_directory <code>create_directory(path)</code> and <code>create_directories(path)</code>] | |||
* [https://en.cppreference.com/w/cpp/filesystem/directory_iterator <code>directory_iterator(path)</code>] | |||
;Notes | |||
* <code>create_directory</code> requires that the parent directory already exists | |||
** If not, use <code>create_directories</code> instead | |||
===Fstream=== | |||
<code>#include <fstream></code><br> | <code>#include <fstream></code><br> | ||
Used for input/output of files | |||
====Reading and Writing==== | ====Reading and Writing==== | ||
Reading and writing is done using <code>fstream</code>.<br> | Reading and writing is done using <code>fstream</code>.<br> | ||
Line 68: | Line 177: | ||
int main() { | int main() { | ||
std:: | std::ifstream my_file("my_file.txt"); | ||
std::string line; | std::string line; | ||
// Read line by line | |||
// You can also read using << | |||
while (getline(my_file, line)) { | while (getline(my_file, line)) { | ||
std::cout << line << std::endl; | std::cout << line << std::endl; | ||
Line 82: | Line 191: | ||
[https://insanecoding.blogspot.com/2011/11/how-to-read-in-file-in-c.html Reference and comparison of different methods] | [https://insanecoding.blogspot.com/2011/11/how-to-read-in-file-in-c.html Reference and comparison of different methods] | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
#include <cerrno> | |||
#include <fstream> | #include <fstream> | ||
#include <string> | #include <string> | ||
#include < | #include <string_view> | ||
std::string get_file_contents( | std::string get_file_contents(std::string_view filename) { | ||
{ | |||
std::ifstream in(filename, std::ios::in | std::ios::binary); | std::ifstream in(filename, std::ios::in | std::ios::binary); | ||
if (in) | if (in.good()) { | ||
std::string contents; | std::string contents; | ||
in.seekg(0, std::ios::end); | in.seekg(0, std::ios::end); | ||
contents.resize(in.tellg()); | contents.resize(static_cast<unsigned int>(in.tellg())); | ||
in.seekg(0, std::ios::beg); | in.seekg(0, std::ios::beg); | ||
in.read(&contents[0], contents.size()); | in.read(&contents[0], contents.size()); | ||
return contents; | |||
return | |||
} | } | ||
std::cerr << "Failed to open file: " << filename << std::endl; | |||
throw(errno); | throw(errno); | ||
} | } | ||
Line 104: | Line 212: | ||
===Regular Expressions=== | ===Regular Expressions=== | ||
<code>#include <regex></code> | <code>#include <regex></code> | ||
[https://en.cppreference.com/w/cpp/regex Reference] | [https://en.cppreference.com/w/cpp/regex Reference] | ||
<!-- | <!-- | ||
<syntaxhighlight lang="C++> | <syntaxhighlight lang="C++> | ||
Line 120: | Line 230: | ||
===Thread=== | ===Thread=== | ||
<code>#include <thread></code><br> | <code>#include <thread></code><br> | ||
[https://en.cppreference.com/w/cpp/thread/thread std::thread reference]<br> | |||
Basic Usage: | |||
<syntaxhighlight lang="C++"> | |||
std::thread my_thread(thread_function); | |||
// Calling methods | |||
// You can also pass in parameters as usual | |||
std::thread my_thread(&Class::method, this)); | |||
// Lambda functions | |||
std::thread my_thread([&]() { | |||
// do something | |||
}); | |||
// Wait for thread to finish | |||
my_thread.join(); | |||
// get id of thread | |||
std::thread::id my_id = my_thread.get_id(); | |||
// get id of this thread | |||
std::thread::id my_id = std::this_thread::get_id(); | |||
</syntaxhighlight> | |||
==== Sleep ==== | ==== Sleep ==== | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
Line 132: | Line 264: | ||
[https://www.geeksforgeeks.org/auto_ptr-unique_ptr-shared_ptr-weak_ptr-2/ Smart Pointers]<br> | [https://www.geeksforgeeks.org/auto_ptr-unique_ptr-shared_ptr-weak_ptr-2/ Smart Pointers]<br> | ||
Smart pointers were added in C++11.<br> | Smart pointers were added in C++11.<br> | ||
There are | There are 3 types of smart pointers: | ||
* <code>unique_ptr</code> | * <code>unique_ptr</code> | ||
* <code>shared_ptr</code> | * <code>shared_ptr</code> | ||
* <code>weak_ptr</code> | * <code>weak_ptr</code> | ||
Use <code>unique_ptr</code> | Use <code>unique_ptr</code> when one piece of code ''owns'' the memory at any given time.<br> | ||
Use <code>shared_ptr</code> when multiple objects need to reference the same thing.<br> | Use <code>shared_ptr</code> when multiple objects need to reference the same thing.<br> | ||
Use <code>weak_ptr</code> to avoid cyclic dependencies which cause issues with reference counting.<br> | Use <code>weak_ptr</code> to avoid cyclic dependencies which cause issues with reference counting.<br> | ||
If you are using C++14 or newer, you should use <code>make_unique</code> or <code>make_shared</code> which will only make one memory allocation for both the object and the pointer rather than two memory allocations.<br> | |||
Alternatively if you already have a smart pointer, you can call <code>my_ptr.reset(new Car())</code> to change the pointer or <code>my_ptr.reset()</code> to deallocate the object referenced by the pointer. | |||
Example: | Example: | ||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
// Block-scope car | |||
Car my_car; | |||
// Old C++ | |||
// Must call delete my_car; to avoid memory leaks. | |||
Car *my_car = new Car(); | |||
// Using unique ptr | |||
std::unique_ptr<Car> my_car(new Car()); | std::unique_ptr<Car> my_car(new Car()); | ||
// Or starting from C++14 | // Or starting from C++14 | ||
auto my_car = std::make_unique<Car>(); | auto my_car = std::make_unique<Car>(); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
;Notes | |||
* If the object you need is not very large, you can consider just including it as part of your class (or leaving it on the stack) rather than use pointers. | |||
* If you want to get a copy of the smart pointer to the current object, the object must '''publically''' inherit <code>std::enable_shared_from_this<T></code> | |||
** Then you can call <code>shared_from_this()</code> from within any method (not the constructor). | |||
** May throw <code>bad_weak_ptr</code> if you call <code>shared_from_this()</code> without <code>make_shared</code> or if you do not publically inherit <code>std::enable_shared_from_this<T></code> | |||
* When writing functions when do not operate on pointers and do not claim ownership of objects, you should just take a reference to the object as the argument. | |||
* <code>std::auto_ptr</code> was a predecessor to <code>std::unique_ptr</code> which allowed copies. It shouldn't be used anymore. | |||
====Garbage Collection==== | ====Garbage Collection==== | ||
Line 175: | Line 325: | ||
There are a few ways to do this: | There are a few ways to do this: | ||
* Use smart pointers | * Use smart pointers | ||
* | * Swap | ||
* Call a clear/shrink/deallocate function | * Call a clear/shrink/deallocate function | ||
Example [https://stackoverflow.com/questions/3054567/right-way-to-deallocate-an-stdvector-object Reference]: | Example [https://stackoverflow.com/questions/3054567/right-way-to-deallocate-an-stdvector-object Reference]: | ||
Line 183: | Line 333: | ||
my_vector.reset(); | my_vector.reset(); | ||
// | // Swap | ||
std::vector<float> my_vector(99); | std::vector<float> my_vector(99); | ||
std::vector<float>().swap(my_vector); | my_vector = std::vector<float>; | ||
// Or alternatively | |||
// std::vector<float>().swap(my_vector); | |||
// std::swap(my_vector, std::vector<float>); | |||
// Swap for cl::Buffer | |||
cl::Buffer my_buf(context, CL_MEM_READ_WRITE, size); | |||
my_buf = cl::Buffer(); | |||
// Clear and shrink | // Clear and shrink | ||
Line 205: | Line 362: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Containers== | ===Utility=== | ||
===unordered_set=== | <code>#include <utility></code><br> | ||
====std::move==== | |||
[https://en.cppreference.com/w/cpp/utility/move Ref]<br> | |||
Use <code>std::move</code> to move containers. | |||
===Algorithm=== | |||
====std::find==== | |||
[https://en.cppreference.com/w/cpp/algorithm/find Reference]<br> | |||
====std::generate==== | |||
[https://en.cppreference.com/w/cpp/algorithm/generate cppreference]<br> | |||
Allows you to fill a container using a function call<br> | |||
<syntaxhighlight lang="cpp"> | |||
#include <random> | |||
#include <iostream> | |||
#include <algorithm> | |||
int main() | |||
{ | |||
std::random_device rd; | |||
std::mt19937 gen(rd()); | |||
# Fill with integers in [0, 10] | |||
std::uniform_int_distribution<> dis(0, 10); | |||
std::vector<int> my_vec(10, 0); | |||
std::generate(my_vec.begin(), my_vec.end(), [&](){return dis(gen);}); | |||
for (int v : my_vec) { | |||
std::cout << v << " "; | |||
} | |||
std::cout << std::endl; | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
===Numeric=== | |||
====std::iota==== | |||
[https://en.cppreference.com/w/cpp/algorithm/iota Reference]<br> | |||
Fills an array or vector with increasing values. Can pass in a starting number. | |||
<syntaxhighlight lang="cpp"> | |||
std::vector<int> v(60); | |||
std::iota(v.begin(), v.end(), 0); | |||
</syntaxhighlight> | |||
====std::accumulate==== | |||
Adds up numbers. Can pass in a starting number. | |||
<syntaxhighlight lang="cpp"> | |||
std::vector<int> v(60); | |||
std::iota(v.begin(), v.end(), 0); | |||
std::accumulate(v.begin(), v.end(), 0); | |||
</syntaxhighlight> | |||
===Chrono=== | |||
<code>#include <chrono></code><br> | |||
I now prefer using <code>absl::Time</code> and <code>absl::Duration</code> over Chrono because they abstract away the underlying type. | |||
<syntaxhighlight lang="cpp"> | |||
auto start = std::chrono::high_resolution_clock::now(); | |||
// do something | |||
auto end = std::chrono::high_resolution_clock::now(); | |||
std::cout << "Time elapsed: " | |||
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() | |||
<< " ms" << std::endl; | |||
</syntaxhighlight> | |||
===Execution=== | |||
<code>#include <execution></code><br> | |||
The execution header gives you tools for parallel execution (since C++17).<br> | |||
See [https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t execution_policy_tag].<br> | |||
[https://devblogs.microsoft.com/cppblog/using-c17-parallel-algorithms-for-better-performance/ C++17 Parallel Algorithms blog].<br> | |||
[https://developer.nvidia.com/blog/accelerating-standard-c-with-gpus-using-stdpar/ Nvidia Accelerating Standard C++ with GPUs Using stdpar]<br> | |||
;Parallel Sorting Example | |||
<syntaxhighlight lang="cpp"> | |||
std::sort(std::execution::par_unseq, sorted.begin(), sorted.end()); | |||
</syntaxhighlight> | |||
* <code>std::execution::seq</code> sequential | |||
* <code>std::execution::unseq</code> vectorized only (C++20) | |||
* <code>std::execution::par</code> parallel | |||
* <code>std::execution::par_unseq</code> parallel and vectorized | |||
===Random=== | |||
<code>#include <random></code><br> | |||
[https://en.cppreference.com/w/cpp/header/random cppreference.com]<br> | |||
<syntaxhighlight lang="cpp"> | |||
std::random_device rd; //Will be used to obtain a seed for the random number engine | |||
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() | |||
std::uniform_int_distribution<> dis(1, 6); | |||
for (int n=0; n<10; ++n) | |||
//Use dis to transform the random unsigned int generated by gen into an int in [1, 6] | |||
std::cout << dis(gen) << ' '; | |||
std::cout << '\n'; | |||
</syntaxhighlight> | |||
===const=== | |||
For variables: | |||
# Use <code>constexpr</code> for values initialized at compile time and won't change at runtime. Most of the time, this is what you want for hardcoded compile time parameters. | |||
# Use <code>const</code> for values initialized at runtime and won't change. | |||
# Use <code>constinit</code> for values initialized at compile time and may change at runtime. I haven't found a use case for this yet. | |||
For functions: | |||
# Add <code>const</code> to the end of a method declaration if it won't change the object. | |||
# Add <code>constexpr</code> if the function can be evaluated at compile time, i.e. can accepts and output <code>constexpr</code> variables. | |||
# Add <code>consteval</code> if you want to force the function to only be evaluated at compile time. | |||
==STL== | |||
STL is the Standard Template Library originally implemented in 1994 by Stepanov and Lee from HP.<br> | |||
STL consists of a general set of algorithms, containers, functions, and iterators.<br> | |||
Today, STL refers to those containers and algorithms which are now built into the standard library (std) of C++. | |||
===Simple Containers=== | |||
====std::pair==== | |||
[https://en.cppreference.com/w/cpp/utility/pair std::pair] | |||
===Sequences=== | |||
====std::array==== | |||
<code>#include <array></code><br> | |||
This wrapper around C-style arrays gives us size information and allows the array to be passed around by reference while keeping the array on the stack or in a struct. | |||
Unless you need stack allocation or allocation into a struct, you are should probably use a vector. | |||
====std::vector==== | |||
<code>#include <vector></code> | |||
https://en.cppreference.com/w/cpp/container/vector<br> | |||
This is a dynamically-allocated resizable array, known as an ArrayList in Java.<br> | |||
<syntaxhighlight lang="c++"> | |||
// Basics | |||
vector my_vec; | |||
// Vector with size 5 | |||
vector my_vec(5); | |||
// Vector with size 5 initialized to 1 | |||
vector my_vec(5, 1); | |||
// Length of vector | |||
my_vec.size(); | |||
// Equivalent to size()==0 | |||
my_vec.empty(); | |||
// Equivalent to my_vec[0]; | |||
// Undefined on empty vectors | |||
my_vec.front(); | |||
// Equivalent to my_vec[my_vec.size()-1]; | |||
// Undefined on empty vectors | |||
my_vec.back(); | |||
</syntaxhighlight> | |||
Note that [https://en.cppreference.com/w/cpp/container/vector_bool <code>vector<bool></code>] is a special case of bit-packed booleans instead of an array of bools. You should use <code>vector<char></code> instead if your code relies on it being continguous.<br> | |||
====std::span==== | |||
<code>#include <span></code><br> | |||
https://en.cppreference.com/w/cpp/container/span<br> | |||
This is view of some contiguous amount of memory. If the size is static, this is equivalent to a single pointer, otherwise is it equivalent to two pointers (i.e. begin and end). | |||
If you use this as the parameter to your function, it will accept both arrays and vectors. | |||
Additionaly, there is a [https://en.cppreference.com/w/cpp/container/span/subspan subspan] function so you don't need to pass around indices or pointers to get subvectors. | |||
====std::deque==== | |||
Double-ended queue | |||
====std::list==== | |||
This is a doubly linked list. You can delete elements from the middle of the list if you know have an iterator. | |||
<syntaxhighlight lang="cpp"> | |||
list<int> m_list; | |||
list<int>::iterator m_it = m_list.insert(5); | |||
// Remove the element | |||
m_list.erase(m_it); | |||
</syntaxhighlight> | |||
===Container adaptors=== | |||
====std::queue==== | |||
[https://en.cppreference.com/w/cpp/container/queue Reference]<br> | |||
<syntaxhighlight lang="c++"> | |||
std::queue<int> my_queue; | |||
my_queue.push(a); | |||
auto val = my_queue.front(); | |||
my_queue.pop(); // returns void | |||
</syntaxhighlight> | |||
====std::stack==== | |||
[https://en.cppreference.com/w/cpp/container/stack cppreference] | |||
<syntaxhighlight lang="cpp"> | |||
std::stack<char> my_stack; | |||
// Push to stack | |||
// You can also use emplace | |||
// Returns void | |||
my_stack.push('a'); | |||
// Peek | |||
// Always make sure stack is not empty | |||
char top = my_stack.top('a'); | |||
// Pop | |||
// Note: returns void | |||
// Always make sure stack is not empty | |||
my_stack.pop(); | |||
</syntaxhighlight> | |||
====std::priority_queue==== | |||
This is a min/max heap. | |||
===Associative Containers=== | |||
Also known as maps or associative arrays. | |||
====std::set==== | |||
[https://en.cppreference.com/w/cpp/container/set reference]<br> | |||
<code>#include<set></code><br> | |||
This is a binary tree (likely red-black tree). You can assume <math>O(\log n)</math> operations. | |||
====std::map==== | |||
[https://en.cppreference.com/w/cpp/container/map reference]<br> | |||
<code>#include<map></code><br> | |||
This is a binary tree (likely red-black tree). You can assume <math>O(\log n)</math> operations. | |||
====std::unordered_set==== | |||
[https://en.cppreference.com/w/cpp/container/unordered_set reference]<br> | |||
<code>#include <unordered_set></code><br> | <code>#include <unordered_set></code><br> | ||
This is a hashset.<br> | This is a hashset. You can assume operations are <math>O(1)</math> on average and <math>O(N)</math> worst case.<br> | ||
<syntaxhighlight lang="cpp> | <syntaxhighlight lang="cpp> | ||
unordered_set<int> my_set; | std::unordered_set<int> my_set; | ||
// | // Add | ||
my_set.insert(5); | my_set.insert(5); | ||
// Check contains | // Check contains | ||
my_set.find(5) != my_set.end(); | my_set.find(5) != my_set.end(); // Before C++20 | ||
my_set.contains(5); // C++20 | |||
// Remove | |||
my_set.erase(5); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
====std::unordered_map==== | |||
[https://en.cppreference.com/w/cpp/container/unordered_map reference]<br> | |||
<code>#include<unordered_map></code><br> | |||
This is a hashmap. You can assume operations are <math>O(1)</math> on average and <math>O(N)</math> worst case.<br> | |||
<syntaxhighlight lang="C++"> | |||
std::unordered_map<int, std::string> my_map; | |||
my_map[5] = "hey"; // Fine as long as value type is not a reference. | |||
my_map.insert({5, "hey"}); // Necessary if value type is a reference. | |||
my_map.find(5) != my_map.end(); | |||
my_map.contains(5); // C++20 | |||
</syntaxhighlight> | |||
;Custom Keys | |||
How to use a rational number as a key in C++ | |||
<syntaxhighlight lang="C++"> | |||
struct Fraction | |||
{ | |||
int num; | |||
int den; | |||
bool operator==(const Fraction &other) const { | |||
return num*other.den == den * other.num; | |||
} | |||
Fraction(int a, int b) : num(a), den(b) {} | |||
}; | |||
</syntaxhighlight> | |||
==Functional Programming== | |||
https://medium.com/swlh/doing-it-the-functional-way-in-c-5c392bbdd46a | |||
Many of these can be parallelized with [https://en.cppreference.com/w/cpp/algorithm/execution_policy_tag_t execution policies] such as <code>std::execution::par</code> and <code>std::execution::par_unseq</code>. Paired with [https://adaptivecpp.github.io/AdaptiveCpp/stdpar/ AdaptiveCPP], some operations can be automatically GPU accelerated as well. | |||
Most of these require C++20. | |||
===Map=== | |||
* <code>std::for_each</code> | |||
* <code>std::transform</code> | |||
* [https://en.cppreference.com/w/cpp/algorithm/copy <code>std::copy</code>, <code>std::copy_if</code>] | |||
* [https://en.cppreference.com/w/cpp/algorithm/fill <code>std::fill</code>] | |||
===Reduce/Fold=== | |||
* <code>std::reduce</code> | |||
* <code>std::accumulate</code> | |||
* [https://en.cppreference.com/w/cpp/algorithm/ranges/fold_left <code>std::ranges::fold_left</code>] (C++23) | |||
===Filter=== | |||
* <code>std::copy_if</code> | |||
* <code>std::remove_if</code> | |||
* <code>std::find_if</code> | |||
==Programming Styles== | ==Programming Styles== | ||
===Modern C++=== | ===Modern C++=== | ||
[https://github.com/rigtorp/awesome-modern-cpp List of resources]<br> | [https://github.com/rigtorp/awesome-modern-cpp List of resources]<br> | ||
* Use RAII principles. | |||
** I.e. each object should manage it's own memory rather than the caller having to manage it. | |||
** You should never use `malloc` and `free` unless interfacing with C libraries. | |||
* Avoid the use of new and delete, instead using vector or smart pointers. | |||
* Use clang-format. | * Use clang-format. | ||
== | ;Resources | ||
== | * [https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md CppCoreGuidelines] | ||
* [https://google.github.io/styleguide/cppguide.html Google C++ Style Guide] - note that some people dislike this since it is focused on interoperability and suggests avoiding exceptions. | |||
==RAII== | |||
[https://en.cppreference.com/w/cpp/language/raii cppreference raii]<br> | |||
[https://en.cppreference.com/w/cpp/language/rule_of_three cppreference rule_of_three]<br> | |||
Resource Acquisition Is Initialization - binds the life cycle of a resource to the lifetime of an object.<br> | |||
For instance, the resource for a vector is an allocated amount of memory. Once the vector is destroyed and the destructor called, the resource is released.<br> | |||
If you need any from one of the rules, you need to implement the remainder | |||
;Rule of zero<br> | |||
Do not use a custom deconstructor, copy constructor, or copy assignment. Push all of these operations into the classes of member variables such as <code>std::vector</code> and <code>unique_ptr</code>. This is the best and simplest case. | |||
;[[Wikipedia: Rule of three (C++ programming) | Rule of three]] | |||
* Destructor | |||
* Copy constructor | |||
* Copy assignment operator | |||
;[[Wikipedia: Rule of three (C++ programming)#Rule of five | Rule of five]] | |||
* All from rule of three plus: | |||
* Move constructor | |||
* Move operator | |||
;Rule of four and a half: | |||
* Destructor | |||
* Copy constructor | |||
* Copy-and-swap assignment operator | |||
* Swap function | |||
{{hidden | Example Rule of Four RAII Class | | |||
Copied from [https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom stack overflow] | |||
<syntaxhighlight lang="cpp"> | |||
#include <algorithm> // std::copy | |||
#include <cstddef> // std::size_t | |||
class dumb_array | |||
{ | |||
public: | |||
// (default) constructor | |||
dumb_array(std::size_t size = 0) | |||
: mSize(size), | |||
mArray(mSize ? new int[mSize]() : nullptr) | |||
{ | |||
} | |||
// copy-constructor | |||
dumb_array(const dumb_array& other) | |||
: mSize(other.mSize), | |||
mArray(mSize ? new int[mSize] : nullptr), | |||
{ | |||
// note that this is non-throwing, because of the data | |||
// types being used; more attention to detail with regards | |||
// to exceptions must be given in a more general case, however | |||
std::copy(other.mArray, other.mArray + mSize, mArray); | |||
} | |||
// destructor | |||
~dumb_array() | |||
{ | |||
delete [] mArray; | |||
} | |||
// assignment operator | |||
dumb_array& operator=(dumb_array other) // (1) | |||
{ | |||
swap(*this, other); // (2) | |||
return *this; | |||
} | |||
// swap | |||
friend void swap(dumb_array& first, dumb_array& second) // nothrow | |||
{ | |||
// enable ADL (not necessary in our case, but good practice) | |||
using std::swap; | |||
// by swapping the members of two objects, | |||
// the two objects are effectively swapped | |||
swap(first.mSize, second.mSize); | |||
swap(first.mArray, second.mArray); | |||
} | |||
private: | |||
std::size_t mSize; | |||
int* mArray; | |||
}; | |||
</syntaxhighlight> | |||
}} | |||
==Useful Libraries== | |||
A list of useful libraries | |||
===Boost=== | |||
{{main | Boost (C++ libraries)}} | |||
A set of popular C++ libraries. Most are header-only. | |||
===cxxopts=== | |||
[https://github.com/jarro2783/cxxopts Link]<br> | |||
A header-only C++ argument parser.<br> | |||
Note that if you already use Boost, you can use <code>Boost::Program_options</code> instead. | |||
===Eigen=== | |||
{{main | Eigen (C++ library)}} | |||
A header-only C++ linear algebra library. | |||
===absl=== | |||
https://github.com/abseil/abseil-cpp is a library used by Google which supplements the standard library. | |||
Useful things: | |||
# <code>absl::Time</code> and <code>absl::Duration</code>. | |||
# [https://abseil.io/docs/cpp/guides/strings#abslstrcat absl strings] | |||
# [https://abseil.io/docs/cpp/guides/logging absl logging] | |||
Many parts of absl now have <code>std::</code> equivalents such as <code>std::unique_ptr</code>, <code>std::string_view</code>, <code>std::span</code>. Unless contributing to Google codebases, you should probably prefer those. | |||
At Google, they prefer absl hash containers over unordered_set and unordered_map: | |||
# <code>absl::flat_hash_map</code> | |||
==References== | |||
[[Category:Programming languages]] |
Latest revision as of 03:38, 13 November 2024
C++ is a very popular and powerful language which includes all the low-level features of C (e.g. pointers, operator overloading) along many high-level features (RAII, STD algorithms, STL containers) thanks to the C++ standard library.
Usage
How to do things using the C++ standard library (stdlib).
Compilation
cmake
g++
g++ my_driver.c [-Iincludefolder] -o my_program.out
Standard optimizations
-std=c++17
for C++17 support-O3
for level 3 optimizations-g
to include debugging info-march=native
- use all instructions available on the current CPU-mtune=native
- optimize for the current CPU
Syntax
Main
All C++ programs launch in a main
function.
Similar to C, the arguments are int argc
and char *argv[]
.
These can be easily converted to a std::vector<std::string>
for convenience.
#include <string>
#include <vector>
int main(int argc, char *argv[]) {
std::vector<std::string> args(argv, argv + argc);
// Your code here
return EXIT_SUCCESS;
}
Headers
C++ includes C-headers such as math.h
and cmath
.
The C-style header will place everything in the global namespace while the C++ header will place everything in std
.
You should use cmath
.
Lambda Expressions
Casting
C++ has several types of casts including:
static_cast
- your standard cast with conversion. Does not perform any checks.dynamic_cast
- for casting objects with checking, requires a polymorphic base class (with a virtual function). Will return nullptr.reinterpret_cast
- cast without any conversion, for directly dealing with binary data, equivalent to*(T*)
in C.
References
References are accepted or store using &
.
For example:
void healPerson(Person &person) {
person.health = 100;
}
References are like pointers since they do not copy the object except they cannot be null and they cannot be reassigned.
Note that primitives can also be used with references, in which case changes will propagate to the underlying value.
You can also use them as class attributes, initializing them in the constructor's initializer list.
To store references in a vector, you can use std::reference_wrapper
and include the functional
header.
Types
For simple programs, you can use the standard types:
int
,uint
,long
,size_t
float
,double
See SO for the standard and guaranteed precision of these built-in types.
C++ also has fixed-width types in #include <cstdint
(since C++11).
cppreference cstdint
I recommend using these for anything with specific or high precision requirements.
Typically, I use:
uint8_t
instead ofchar
orstd::byte
.int64_t
instead oflong long
String
#include <string>
If you don't need to own the string, prefer to use string_view
.
// c-str to string
char *old_string = "my c-style string";
string cpp_string(old_string);
// string to c-str
cpp_string.c_str();
// char to string
char my_char = 'a';
string my_str(1, my_char);
String Interpolation
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string a = "a", b = "b", c = "c";
// apply formatting
std::stringstream s;
s << a << " " << b << " > " << c;
// assign to std::string
std::string str = s.str();
std::cout << str << "\n";
}
Buildings Strings
The Complete Guide to Building Strings In C++
There are multiple ways of buildings strings in C++.
Strings are mutable in C++.
I typically use +
or ostringstream
to build strings.
std::basic_string_view
This is useful for writing functions which accept anything that looks like a string such as substrings, since typically std::string::substr
performs a copy.
Note that std::string_view
is std::basic_string_view<char>
.
Filesystem
#include <filesystem>
Convenient functions for filesystem. Added since C++17.
Path
Note if you use g++ <= version 9, you will need to add the flag -lstdc++fs
.
using std::filesystem::path;
// Initialization
path my_path = "my_dir/my_file";
// or my_path = path("my_dir") / "my_file";
// Append to path
path("foo") / "bar"; // path("foo/bar")
path("foo") / "/bar"; // path("/bar")
// Print
std::cout << my_path << std::endl; // prints "my_dir/my_file" with quotes
std::cout << my_path.string() << std::endl; // prints my_dir/my_file without quotes
- Notes
path
supports implicit conversion tostring
Directories
- Notes
create_directory
requires that the parent directory already exists- If not, use
create_directories
instead
- If not, use
Fstream
#include <fstream>
Used for input/output of files
Reading and Writing
Reading and writing is done using fstream
.
If you don't need r/w, use istream
for reading or ostream
for writing.
#include <iostream>
#include <fstream>
int main() {
std::ifstream my_file("my_file.txt");
std::string line;
// Read line by line
// You can also read using <<
while (getline(my_file, line)) {
std::cout << line << std::endl;
}
return 0;
}
Reading a whole file
Reference and comparison of different methods
#include <cerrno>
#include <fstream>
#include <string>
#include <string_view>
std::string get_file_contents(std::string_view filename) {
std::ifstream in(filename, std::ios::in | std::ios::binary);
if (in.good()) {
std::string contents;
in.seekg(0, std::ios::end);
contents.resize(static_cast<unsigned int>(in.tellg()));
in.seekg(0, std::ios::beg);
in.read(&contents[0], contents.size());
return contents;
}
std::cerr << "Failed to open file: " << filename << std::endl;
throw(errno);
}
Regular Expressions
#include <regex>
Thread
#include <thread>
std::thread reference
Basic Usage:
std::thread my_thread(thread_function);
// Calling methods
// You can also pass in parameters as usual
std::thread my_thread(&Class::method, this));
// Lambda functions
std::thread my_thread([&]() {
// do something
});
// Wait for thread to finish
my_thread.join();
// get id of thread
std::thread::id my_id = my_thread.get_id();
// get id of this thread
std::thread::id my_id = std::this_thread::get_id();
Sleep
std::this_thread::sleep_for(std::chrono::milliseconds(1));
Parallel For
Memory
#include <memory>
Smart Pointers
Smart Pointers
Smart pointers were added in C++11.
There are 3 types of smart pointers:
unique_ptr
shared_ptr
weak_ptr
Use unique_ptr
when one piece of code owns the memory at any given time.
Use shared_ptr
when multiple objects need to reference the same thing.
Use weak_ptr
to avoid cyclic dependencies which cause issues with reference counting.
If you are using C++14 or newer, you should use make_unique
or make_shared
which will only make one memory allocation for both the object and the pointer rather than two memory allocations.
Alternatively if you already have a smart pointer, you can call my_ptr.reset(new Car())
to change the pointer or my_ptr.reset()
to deallocate the object referenced by the pointer.
Example:
// Block-scope car
Car my_car;
// Old C++
// Must call delete my_car; to avoid memory leaks.
Car *my_car = new Car();
// Using unique ptr
std::unique_ptr<Car> my_car(new Car());
// Or starting from C++14
auto my_car = std::make_unique<Car>();
- Notes
- If the object you need is not very large, you can consider just including it as part of your class (or leaving it on the stack) rather than use pointers.
- If you want to get a copy of the smart pointer to the current object, the object must publically inherit
std::enable_shared_from_this<T>
- Then you can call
shared_from_this()
from within any method (not the constructor). - May throw
bad_weak_ptr
if you callshared_from_this()
withoutmake_shared
or if you do not publically inheritstd::enable_shared_from_this<T>
- Then you can call
- When writing functions when do not operate on pointers and do not claim ownership of objects, you should just take a reference to the object as the argument.
std::auto_ptr
was a predecessor tostd::unique_ptr
which allowed copies. It shouldn't be used anymore.
Garbage Collection
Starting from C++11, you should use smart pointers such as shared_ptr
which have automatic garbage collection.
Traditional C++ does not have garbage collection.
After using new
to allocate an object, use delete
to deallocate it.
You can also use C allocation with malloc
, calloc
, alloca
, and free
, though it is not recommended since these are not type-safe.
Custom Deleter
Custom Deleters
When using smart pointers, the default deleter is the delete
function but you can also specify your own deleter.
# Using a functor
struct AVFrameDeleter {
void operator()(AVFrame *p) { av_frame_free(&p); }
};
std::unique_ptr<AVFrame, AVFrameDeleter> rgb_frame(av_frame_alloc());
# Using free
std::unique_ptr<void *, decltype(std::free) *> my_buffer(std::malloc(10), std::free);
Deallocate
Normally, containers such as std::vector
will automatically deallocate memory from the heap when the destructor is called. However, occationally you may want to coerse this deallocation yourself.
There are a few ways to do this:
- Use smart pointers
- Swap
- Call a clear/shrink/deallocate function
Example Reference:
// Using smart pointers
std::unique_ptr<std::vector<float>> my_vector = make_unique<std::vector<float>>(99);
my_vector.reset();
// Swap
std::vector<float> my_vector(99);
my_vector = std::vector<float>;
// Or alternatively
// std::vector<float>().swap(my_vector);
// std::swap(my_vector, std::vector<float>);
// Swap for cl::Buffer
cl::Buffer my_buf(context, CL_MEM_READ_WRITE, size);
my_buf = cl::Buffer();
// Clear and shrink
// Specific to std::vector
std::vector<float> my_vector(99);
my_vector.clear();
my_vector.shrink_to_fit();
Limits
#include <limits>
Reference
C++ has standard macros such as INT_MAX
.
The limits header adds these limits for every type.
// Equivalent to FLT_MAX
std::numeric_limits<float>::max();
Utility
#include <utility>
std::move
Ref
Use std::move
to move containers.
Algorithm
std::find
std::generate
cppreference
Allows you to fill a container using a function call
#include <random>
#include <iostream>
#include <algorithm>
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
# Fill with integers in [0, 10]
std::uniform_int_distribution<> dis(0, 10);
std::vector<int> my_vec(10, 0);
std::generate(my_vec.begin(), my_vec.end(), [&](){return dis(gen);});
<br />
for (int v : my_vec) {
std::cout << v << " ";
}
std::cout << std::endl;
return 0;
}
Numeric
std::iota
Reference
Fills an array or vector with increasing values. Can pass in a starting number.
std::vector<int> v(60);
std::iota(v.begin(), v.end(), 0);
std::accumulate
Adds up numbers. Can pass in a starting number.
std::vector<int> v(60);
std::iota(v.begin(), v.end(), 0);
std::accumulate(v.begin(), v.end(), 0);
Chrono
#include <chrono>
I now prefer using absl::Time
and absl::Duration
over Chrono because they abstract away the underlying type.
auto start = std::chrono::high_resolution_clock::now();
// do something
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Time elapsed: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
<< " ms" << std::endl;
Execution
#include <execution>
The execution header gives you tools for parallel execution (since C++17).
See execution_policy_tag.
C++17 Parallel Algorithms blog.
Nvidia Accelerating Standard C++ with GPUs Using stdpar
- Parallel Sorting Example
std::sort(std::execution::par_unseq, sorted.begin(), sorted.end());
std::execution::seq
sequentialstd::execution::unseq
vectorized only (C++20)std::execution::par
parallelstd::execution::par_unseq
parallel and vectorized
Random
#include <random>
cppreference.com
std::random_device rd; //Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> dis(1, 6);
for (int n=0; n<10; ++n)
//Use dis to transform the random unsigned int generated by gen into an int in [1, 6]
std::cout << dis(gen) << ' ';
std::cout << '\n';
const
For variables:
- Use
constexpr
for values initialized at compile time and won't change at runtime. Most of the time, this is what you want for hardcoded compile time parameters. - Use
const
for values initialized at runtime and won't change. - Use
constinit
for values initialized at compile time and may change at runtime. I haven't found a use case for this yet.
For functions:
- Add
const
to the end of a method declaration if it won't change the object. - Add
constexpr
if the function can be evaluated at compile time, i.e. can accepts and outputconstexpr
variables. - Add
consteval
if you want to force the function to only be evaluated at compile time.
STL
STL is the Standard Template Library originally implemented in 1994 by Stepanov and Lee from HP.
STL consists of a general set of algorithms, containers, functions, and iterators.
Today, STL refers to those containers and algorithms which are now built into the standard library (std) of C++.
Simple Containers
std::pair
Sequences
std::array
#include <array>
This wrapper around C-style arrays gives us size information and allows the array to be passed around by reference while keeping the array on the stack or in a struct.
Unless you need stack allocation or allocation into a struct, you are should probably use a vector.
std::vector
#include <vector>
https://en.cppreference.com/w/cpp/container/vector
This is a dynamically-allocated resizable array, known as an ArrayList in Java.
// Basics
vector my_vec;
// Vector with size 5
vector my_vec(5);
// Vector with size 5 initialized to 1
vector my_vec(5, 1);
// Length of vector
my_vec.size();
// Equivalent to size()==0
my_vec.empty();
// Equivalent to my_vec[0];
// Undefined on empty vectors
my_vec.front();
// Equivalent to my_vec[my_vec.size()-1];
// Undefined on empty vectors
my_vec.back();
Note that vector<bool>
is a special case of bit-packed booleans instead of an array of bools. You should use vector<char>
instead if your code relies on it being continguous.
std::span
#include <span>
https://en.cppreference.com/w/cpp/container/span
This is view of some contiguous amount of memory. If the size is static, this is equivalent to a single pointer, otherwise is it equivalent to two pointers (i.e. begin and end).
If you use this as the parameter to your function, it will accept both arrays and vectors.
Additionaly, there is a subspan function so you don't need to pass around indices or pointers to get subvectors.
std::deque
Double-ended queue
std::list
This is a doubly linked list. You can delete elements from the middle of the list if you know have an iterator.
list<int> m_list;
list<int>::iterator m_it = m_list.insert(5);
// Remove the element
m_list.erase(m_it);
Container adaptors
std::queue
std::queue<int> my_queue;
my_queue.push(a);
auto val = my_queue.front();
my_queue.pop(); // returns void
std::stack
std::stack<char> my_stack;
// Push to stack
// You can also use emplace
// Returns void
my_stack.push('a');
// Peek
// Always make sure stack is not empty
char top = my_stack.top('a');
// Pop
// Note: returns void
// Always make sure stack is not empty
my_stack.pop();
std::priority_queue
This is a min/max heap.
Associative Containers
Also known as maps or associative arrays.
std::set
reference
#include<set>
This is a binary tree (likely red-black tree). You can assume \(\displaystyle O(\log n)\) operations.
std::map
reference
#include<map>
This is a binary tree (likely red-black tree). You can assume \(\displaystyle O(\log n)\) operations.
std::unordered_set
reference
#include <unordered_set>
This is a hashset. You can assume operations are \(\displaystyle O(1)\) on average and \(\displaystyle O(N)\) worst case.
std::unordered_set<int> my_set;
// Add
my_set.insert(5);
// Check contains
my_set.find(5) != my_set.end(); // Before C++20
my_set.contains(5); // C++20
// Remove
my_set.erase(5);
std::unordered_map
reference
#include<unordered_map>
This is a hashmap. You can assume operations are \(\displaystyle O(1)\) on average and \(\displaystyle O(N)\) worst case.
std::unordered_map<int, std::string> my_map;
my_map[5] = "hey"; // Fine as long as value type is not a reference.
my_map.insert({5, "hey"}); // Necessary if value type is a reference.
my_map.find(5) != my_map.end();
my_map.contains(5); // C++20
- Custom Keys
How to use a rational number as a key in C++
struct Fraction
{
int num;
int den;
bool operator==(const Fraction &other) const {
return num*other.den == den * other.num;
}
Fraction(int a, int b) : num(a), den(b) {}
};
Functional Programming
https://medium.com/swlh/doing-it-the-functional-way-in-c-5c392bbdd46a
Many of these can be parallelized with execution policies such as std::execution::par
and std::execution::par_unseq
. Paired with AdaptiveCPP, some operations can be automatically GPU accelerated as well.
Most of these require C++20.
Map
std::for_each
std::transform
std::copy
,std::copy_if
std::fill
Reduce/Fold
std::reduce
std::accumulate
std::ranges::fold_left
(C++23)
Filter
std::copy_if
std::remove_if
std::find_if
Programming Styles
Modern C++
- Use RAII principles.
- I.e. each object should manage it's own memory rather than the caller having to manage it.
- You should never use `malloc` and `free` unless interfacing with C libraries.
- Avoid the use of new and delete, instead using vector or smart pointers.
- Use clang-format.
- Resources
- CppCoreGuidelines
- Google C++ Style Guide - note that some people dislike this since it is focused on interoperability and suggests avoiding exceptions.
RAII
cppreference raii
cppreference rule_of_three
Resource Acquisition Is Initialization - binds the life cycle of a resource to the lifetime of an object.
For instance, the resource for a vector is an allocated amount of memory. Once the vector is destroyed and the destructor called, the resource is released.
If you need any from one of the rules, you need to implement the remainder
- Rule of zero
Do not use a custom deconstructor, copy constructor, or copy assignment. Push all of these operations into the classes of member variables such as std::vector
and unique_ptr
. This is the best and simplest case.
- Destructor
- Copy constructor
- Copy assignment operator
- All from rule of three plus:
- Move constructor
- Move operator
- Rule of four and a half
- Destructor
- Copy constructor
- Copy-and-swap assignment operator
- Swap function
Copied from stack overflow
#include <algorithm> // std::copy
#include <cstddef> // std::size_t
class dumb_array
{
public:
// (default) constructor
dumb_array(std::size_t size = 0)
: mSize(size),
mArray(mSize ? new int[mSize]() : nullptr)
{
}
// copy-constructor
dumb_array(const dumb_array& other)
: mSize(other.mSize),
mArray(mSize ? new int[mSize] : nullptr),
{
// note that this is non-throwing, because of the data
// types being used; more attention to detail with regards
// to exceptions must be given in a more general case, however
std::copy(other.mArray, other.mArray + mSize, mArray);
}
// destructor
~dumb_array()
{
delete [] mArray;
}
// assignment operator
dumb_array& operator=(dumb_array other) // (1)
{
swap(*this, other); // (2)
return *this;
}
// swap
friend void swap(dumb_array& first, dumb_array& second) // nothrow
{
// enable ADL (not necessary in our case, but good practice)
using std::swap;
// by swapping the members of two objects,
// the two objects are effectively swapped
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);
}
private:
std::size_t mSize;
int* mArray;
};
Useful Libraries
A list of useful libraries
Boost
A set of popular C++ libraries. Most are header-only.
cxxopts
Link
A header-only C++ argument parser.
Note that if you already use Boost, you can use Boost::Program_options
instead.
Eigen
A header-only C++ linear algebra library.
absl
https://github.com/abseil/abseil-cpp is a library used by Google which supplements the standard library.
Useful things:
absl::Time
andabsl::Duration
.- absl strings
- absl logging
Many parts of absl now have std::
equivalents such as std::unique_ptr
, std::string_view
, std::span
. Unless contributing to Google codebases, you should probably prefer those.
At Google, they prefer absl hash containers over unordered_set and unordered_map:
absl::flat_hash_map