Capture of this pointer in lambdas has changed constantly since C++11. The attached image captures what has changed in each version of the standard.
While reading the image, remember the following two things:
. [this] is semantically equivalent to [&(*this)] i.e. when the lambda capture includes this, it means a by reference capture of the object pointed to by this pointer. No copies of the current object are made.
. [*this] is equivalent to a by value capture of the object pointed to by this pointer. A copy of the current object is made.
While reading the image, remember the following two things:
. [this] is semantically equivalent to [&(*this)] i.e. when the lambda capture includes this, it means a by reference capture of the object pointed to by this pointer. No copies of the current object are made.
. [*this] is equivalent to a by value capture of the object pointed to by this pointer. A copy of the current object is made.
π3
What is "Deducing this" feature slated for C++23 and how does it help?
https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/
In the blog above, Sy Brand (one of the authors of std::optional) explains what this feature brings to the table. They have summarized all of "Deducing this"s use cases in one of the best written blogs in recent past.
https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/
In the blog above, Sy Brand (one of the authors of std::optional) explains what this feature brings to the table. They have summarized all of "Deducing this"s use cases in one of the best written blogs in recent past.
Microsoft News
C++23βs Deducing this: what it is, why it is, how to use it
Find out how C++23's Deducing this feature can help make your code better.
π2
What are Template Deduction Guides (introduced in C++17) and when and how to use them?
Template deduction guides are patterns associated with a template class that tell the compiler how to translate a set of constructor arguments (and their types) into template parameters for the class.
The simplest example is that of std::vector and its constructor that takes an iterator pair.
The compiler needs to figure out what
You use a deduction guide:
This tells the compiler that, when you call a vector constructor matching that pattern, it will deduce the vector specialization using the code on the right of ->.
You need guides when the deduction of the type from the arguments is not based on the type of one of those arguments. Initializing a vector from an
The left side doesn't necessarily specify an actual constructor. The way it works is that, if you use template constructor deduction on a type, it matches the arguments you pass against all deduction guides (actual constructors of the primary template provide implicit guides). If there is a match, it uses that to determine which template arguments to provide to the type.
But once that deduction is done, once the compiler figures out the template parameters for the type, initialization for the object of that type proceeds as if none of that happened. That is, the deduction guide selected does not have to match the constructor selected.
This also means that you can use guides with aggregates and aggregate initialization:
So deduction guides are only used to figure out the type being initialized. The actual process of initialization works exactly as it did before, once that determination has been made.
Template deduction guides are patterns associated with a template class that tell the compiler how to translate a set of constructor arguments (and their types) into template parameters for the class.
The simplest example is that of std::vector and its constructor that takes an iterator pair.
template<typename Iterator>
void func(Iterator first, Iterator last)
{
vector v(first, last);
}
The compiler needs to figure out what
vector<T>s T type will be. We know what the answer is; T should be typename std::iterator_traits<Iterator>::value_type. But how do we tell the compiler without having to type vector<typename std::iterator_traits<Iterator>::value_type>?You use a deduction guide:
template<typename Iterator> vector(Iterator b, Iterator e) ->
vector<typename std::iterator_traits<Iterator>::value_type>;
This tells the compiler that, when you call a vector constructor matching that pattern, it will deduce the vector specialization using the code on the right of ->.
You need guides when the deduction of the type from the arguments is not based on the type of one of those arguments. Initializing a vector from an
initializer_list explicitly uses the vector's T, so it doesn't need a guide.The left side doesn't necessarily specify an actual constructor. The way it works is that, if you use template constructor deduction on a type, it matches the arguments you pass against all deduction guides (actual constructors of the primary template provide implicit guides). If there is a match, it uses that to determine which template arguments to provide to the type.
But once that deduction is done, once the compiler figures out the template parameters for the type, initialization for the object of that type proceeds as if none of that happened. That is, the deduction guide selected does not have to match the constructor selected.
This also means that you can use guides with aggregates and aggregate initialization:
template<typename T>
struct Thingy
{
T t;
};
Thingy(const char *) -> Thingy<std::string>;
Thingy thing{"A String"}; //thing.t is a `std::string`.
So deduction guides are only used to figure out the type being initialized. The actual process of initialization works exactly as it did before, once that determination has been made.
π4
Linker errors are so hard to understand because of the mangled names. Is there a way to see the actual function names instead of these weird mangled names?
When you compile and link your code, you can pass an additional option
to the linker specifying whether any error output from the linker should
show demangled names or mangled names.
Consider the following code:
we will get the following linker error:
If we don't want to see function names like
The linker error would now look like this:
The -Wl option is used to pass flags to the linker. The flag passed to the linker in this case is --demangle which indicates to the linker that any output from it should demangle the
names.
When you compile and link your code, you can pass an additional option
to the linker specifying whether any error output from the linker should
show demangled names or mangled names.
Consider the following code:
void foo();
void foo(int);
int main() {
foo();
foo(5);
}
If we compile it like this:c++ main.cpp -o mainwe will get the following linker error:
main.o: In function`main':
main.cpp:(.text+0x5): undefined reference to `_Z3foov'
main.cpp:(.text+0xf): undefined reference to `_Z3fooi'
collect2: error: ld returned 1 exit status
If we don't want to see function names like
_Z3foov and _Z3fooi, we can pass an additional flag to the linker asking it to demangle the names in its error output like this:c++ main.cpp -Wl,--demangle -o mainThe linker error would now look like this:
main.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `foo()'
main.cpp:(.text+0xf): undefined reference to `foo(int)'
collect2: error: ld returned 1 exit status
The -Wl option is used to pass flags to the linker. The flag passed to the linker in this case is --demangle which indicates to the linker that any output from it should demangle the
names.
π11
What is Standard Memory Model that was introduced in C++11 and how does it help?
The C++ specification does not make reference to any particular compiler, operating system, or CPU. It makes reference to an abstract machine that is a generalization of actual systems. In the Language Lawyer world, the job of the programmer is to write code for the abstract machine; the job of the compiler is to actualize that code on a concrete machine. By coding rigidly to the spec, you can be certain that your code will compile and run without modification on any system with a compliant C++ compiler, whether today or 50 years from now.
The abstract machine in the C++98/C++03 specification is fundamentally single-threaded. So it is not possible to write multi-threaded C++ code that is "fully portable" with respect to the spec. The spec does not even say anything about the atomicity of memory loads and stores or the order in which loads and stores might happen, never mind things like mutexes.
Of course, you can write multi-threaded code in practice for particular concrete systems β like pthreads or Windows. But there is no standard way to write multi-threaded code for C++98/C++03.
The abstract machine in C++11 is multi-threaded by design. It also has a well-defined memory model; that is, it says what the compiler may and may not do when it comes to accessing memory.
Consider the following example, where a pair of global variables are accessed concurrently by two threads:
What might Thread 2 output?
Under C++98/C++03, this is not even Undefined Behavior; the question itself is meaningless because the standard does not contemplate anything called a "thread".
Under C++11, the result is Undefined Behavior, because loads and stores need not be atomic in general. Which may not seem like much of an improvement. And by itself, it's not.
But with C++11, you can write this:
Now things get much more interesting. First of all, the behavior here is defined. Thread 2 could now print 0 0 (if it runs before Thread 1), 37 17 (if it runs after Thread 1), or 0 17 (if it runs after Thread 1 assigns to x but before it assigns to y).
What it cannot print is 37 0, because the default mode for atomic loads/stores in C++11 is to enforce sequential consistency. This just means all loads and stores must be "as if" they happened in the order you wrote them within each thread, while operations among threads can be interleaved however the system likes. So the default behavior of atomics provides both atomicity and ordering for loads and stores.
Now, on a modern CPU, ensuring sequential consistency can be expensive. In particular, the compiler is likely to emit full-blown memory barriers between every access here. But if your algorithm can tolerate out-of-order loads and stores; i.e., if it requires atomicity but not ordering; i.e., if it can tolerate 37 0 as output from this program, then you can write this:
The more modern the CPU, the more likely this is to be faster than the previous example.
Finally, if you just need to keep particular loads and stores in order, you can write:
This takes us back to the ordered loads and stores β so 37 0 is no longer a possible output β but it does so with minimal overhead. (In this trivial example, the result is the same as full-blown sequential
The C++ specification does not make reference to any particular compiler, operating system, or CPU. It makes reference to an abstract machine that is a generalization of actual systems. In the Language Lawyer world, the job of the programmer is to write code for the abstract machine; the job of the compiler is to actualize that code on a concrete machine. By coding rigidly to the spec, you can be certain that your code will compile and run without modification on any system with a compliant C++ compiler, whether today or 50 years from now.
The abstract machine in the C++98/C++03 specification is fundamentally single-threaded. So it is not possible to write multi-threaded C++ code that is "fully portable" with respect to the spec. The spec does not even say anything about the atomicity of memory loads and stores or the order in which loads and stores might happen, never mind things like mutexes.
Of course, you can write multi-threaded code in practice for particular concrete systems β like pthreads or Windows. But there is no standard way to write multi-threaded code for C++98/C++03.
The abstract machine in C++11 is multi-threaded by design. It also has a well-defined memory model; that is, it says what the compiler may and may not do when it comes to accessing memory.
Consider the following example, where a pair of global variables are accessed concurrently by two threads:
//Global
int x, y;
//Thread 1
x = 17;
y = 37;
//Thread 2
cout << y << " ";
cout << x << endl;
What might Thread 2 output?
Under C++98/C++03, this is not even Undefined Behavior; the question itself is meaningless because the standard does not contemplate anything called a "thread".
Under C++11, the result is Undefined Behavior, because loads and stores need not be atomic in general. Which may not seem like much of an improvement. And by itself, it's not.
But with C++11, you can write this:
\\Global
atomic<int> x, y;
\\Thread 1
x.store(17);
y.store(37);
\\Thread 2
cout << y.load() << " ";
cout << x.load() << endl;
Now things get much more interesting. First of all, the behavior here is defined. Thread 2 could now print 0 0 (if it runs before Thread 1), 37 17 (if it runs after Thread 1), or 0 17 (if it runs after Thread 1 assigns to x but before it assigns to y).
What it cannot print is 37 0, because the default mode for atomic loads/stores in C++11 is to enforce sequential consistency. This just means all loads and stores must be "as if" they happened in the order you wrote them within each thread, while operations among threads can be interleaved however the system likes. So the default behavior of atomics provides both atomicity and ordering for loads and stores.
Now, on a modern CPU, ensuring sequential consistency can be expensive. In particular, the compiler is likely to emit full-blown memory barriers between every access here. But if your algorithm can tolerate out-of-order loads and stores; i.e., if it requires atomicity but not ordering; i.e., if it can tolerate 37 0 as output from this program, then you can write this:
\\Global
atomic<int> x, y;
\\Thread 1
x.store(17,memory_order_relaxed);
y.store(37,memory_order_relaxed);
\\Thread 2
cout << y.load(memory_order_relaxed) << " ";
cout << x.load(memory_order_relaxed) << endl;
The more modern the CPU, the more likely this is to be faster than the previous example.
Finally, if you just need to keep particular loads and stores in order, you can write:
\\Global
atomic<int> x, y;
\\Thread 1
x.store(17,memory_order_release);
y.store(37,memory_order_release);
\\Thread 2
cout << y.load(memory_order_acquire) << " ";
cout << x.load(memory_order_acquire) << endl;
This takes us back to the ordered loads and stores β so 37 0 is no longer a possible output β but it does so with minimal overhead. (In this trivial example, the result is the same as full-blown sequential
π7
in a larger program, it would not be.)
Of course, if the only outputs you want to see are 0 0 or 37 17, you can just wrap a mutex around the original code. But if you have read this far, I bet you already know how that works, and this answer is already longer than I intended :-).
So, bottom line. Mutexes are great, and C++11 standardizes them. But sometimes for performance reasons you want lower-level primitives (e.g., the classic double-checked locking pattern). The new standard provides high-level gadgets like mutexes and condition variables, and it also provides low-level gadgets like atomic types and the various flavors of memory barrier. So now you can write sophisticated, high-performance concurrent routines entirely within the language specified by the standard, and you can be certain your code will compile and run unchanged on both today's systems and tomorrow's.
Although to be frank, unless you are an expert and working on some serious low-level code, you should probably stick to mutexes and condition variables.
For more on this stuff, see this blog post.
Of course, if the only outputs you want to see are 0 0 or 37 17, you can just wrap a mutex around the original code. But if you have read this far, I bet you already know how that works, and this answer is already longer than I intended :-).
So, bottom line. Mutexes are great, and C++11 standardizes them. But sometimes for performance reasons you want lower-level primitives (e.g., the classic double-checked locking pattern). The new standard provides high-level gadgets like mutexes and condition variables, and it also provides low-level gadgets like atomic types and the various flavors of memory barrier. So now you can write sophisticated, high-performance concurrent routines entirely within the language specified by the standard, and you can be certain your code will compile and run unchanged on both today's systems and tomorrow's.
Although to be frank, unless you are an expert and working on some serious low-level code, you should probably stick to mutexes and condition variables.
For more on this stuff, see this blog post.
π4
A collection of lock free and concurrent data structures.
https://github.com/mpoeter/xenium
I have used some of them as a reference implementation and I can vouch for the code quality.
https://github.com/mpoeter/xenium
I have used some of them as a reference implementation and I can vouch for the code quality.
GitHub
GitHub - mpoeter/xenium: A C++ library providing various concurrent data structures and reclamation schemes.
A C++ library providing various concurrent data structures and reclamation schemes. - mpoeter/xenium
π5
Collection of various algorithms in mathematics, machine learning, computer science, physics, etc implemented in C++ for educational purposes.
https://github.com/TheAlgorithms/C-Plus-Plus
https://github.com/TheAlgorithms/C-Plus-Plus
GitHub
GitHub - TheAlgorithms/C-Plus-Plus: Collection of various algorithms in mathematics, machine learning, computer science and physicsβ¦
Collection of various algorithms in mathematics, machine learning, computer science and physics implemented in C++ for educational purposes. - TheAlgorithms/C-Plus-Plus
π5
How can I use RAII to manage resources programmed using a C-style API?
There is an easy way to use RAII to manage resources from a C-style interface: the standard library's smart pointers, which come in two flavors:
If you want simple, scope-based resource management,
You can do much the same with
Now, both of these are all well and good, but the standard library has
GotW#56 mentions that the evaluation of arguments to a function are unordered, which means if you have a function that takes your shiny new
means that the instructions might be ordered like this:
Now our precious C interface resource won't be cleaned up properly if step 2 throws because the
Again as mentioned in GotW #56, there's actually a relatively simple way to deal with the exception safety problem. Unlike expression evaluations in function arguments, function evaluations can't be interleaved. So if we acquire a resource and give it to a
You can use it like this:
and call func worry-free, like this:
The compiler can't take the construction of
There is an easy way to use RAII to manage resources from a C-style interface: the standard library's smart pointers, which come in two flavors:
std::unique_ptr for resources with a single owner and the team of std::shared_ptr and std::weak_ptr for shared resources. If you're having trouble deciding which your resource is, this FAQ should help you decide. Accessing the raw pointer managed by a smart pointer is as easy as calling its get member function.If you want simple, scope-based resource management,
std::unique_ptr is an excellent tool for the job. It's designed for minimal overhead, and is easy to set up to use custom destruction logic. So easy, in fact, that you can do it when you declare the resource variable:
#include <memory> // allow use of smart pointers
struct CStyleResource; // c-style resource
// resource lifetime management functions
CStyleResource* acquireResource(const char *, char*, int);
void releaseResource(CStyleResource* resource);
// my code:
std::unique_ptr<CStyleResource, decltype(&releaseResource)>
resource{acquireResource("name", nullptr, 0), releaseResource};
acquireResource executes where you call it, at the start of the variable's lifetime. releaseResource will execute at the end of the variable's lifetime, usually when it goes out of scope. You can do much the same with
std::shared_ptr, if you require that brand of resource lifetime instead:
// my code:
std::shared_ptr<CStyleResource>
resource{acquireResource("name", nullptr, 0), releaseResource};
Now, both of these are all well and good, but the standard library has
std::make_unique and std::make_shared and one of the reasons is further exception-safety.GotW#56 mentions that the evaluation of arguments to a function are unordered, which means if you have a function that takes your shiny new
std::unique_ptr type and some resource that might throw on construction, supplying that resource to a function call like this:
func(
std::unique_ptr<CStyleResource, decltype(&releaseResource)>{
acquireResource("name", nullptr, 0),
releaseResource},
ThrowsOnConstruction{});
means that the instructions might be ordered like this:
1. call acquireResource
2. construct ThrowsOnConstruction
3. construct std::unique_ptr from resource pointer
Now our precious C interface resource won't be cleaned up properly if step 2 throws because the
unique_ptr hasn't been constructed yet..Again as mentioned in GotW #56, there's actually a relatively simple way to deal with the exception safety problem. Unlike expression evaluations in function arguments, function evaluations can't be interleaved. So if we acquire a resource and give it to a
unique_ptr inside a function, we'll be guaranteed no tricky business will happen to leak our resource when ThrowsOnConstruction throws on construction. We can't use std::make_unique, because it returns a std::unique_ptr with a default deleter, and we want our own custom flavor of deleter. We also want to specify our resource acquisition function, since it can't be deduced from the type without additional code. Implementing such a thing is simple enough with the power of templates.
#include <memory> // smart pointers
#include <utility> // std::forward
template <
typename T,
typename Deletion,
typename Acquisition,
typename...Args>
std::unique_ptr<T, Deletion> make_c_handler(
Acquisition acquisition,
Deletion deletion,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), deletion};
}
You can use it like this:
auto resource = make_c_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0);
and call func worry-free, like this:
func(
make_c_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0),
ThrowsOnConstruction{});
The compiler can't take the construction of
ThrowsOnConstruction and stick it between the call to acquireResource and the construction of the unique_ptr, so you're good.π5
The
Use is once again similar to the
and
There's a further improvement you can make to the use of
Then you can modify make_c_handler like so:
The usage syntax then changes slightly, to
shared_ptr equivalent is similarly simple: just swap out the std::unique_ptr<T, Deletion> return value with std::shared_ptr<T>, and change the name to indicate a shared resource:
template <
typename T,
typename Deletion,
typename Acquisition,
typename...Args>
std::shared_ptr<T> make_c_shared_handler(
Acquisition acquisition,
Deletion deletion,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), deletion};
}
Use is once again similar to the
unique_ptr version:
auto resource = make_c_shared_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0);
and
func(
make_c_shared_handler<CStyleResource>(
acquireResource, releaseResource, "name", nullptr, 0),
ThrowsOnConstruction{});
There's a further improvement you can make to the use of
std::unique_ptr: specifying the deletion mechanism at compile time so the unique_ptr doesn't need to carry a function pointer to the deleter when it's moved around the program. Making a stateless deleter templated on the function pointer you're using requires four lines of code, placed before make_c_handler:
template <typename T, void (*Func)(T*)>
struct CDeleter{
void operator()(T* t){Func(t);}
};
Then you can modify make_c_handler like so:
template <
typename T,
void (*Deleter)(T*),
typename Acquisition,
typename...Args>
std::unique_ptr<T, CDeleter<T, Deleter>> make_c_handler(
Acquisition acquisition,
Args&&...args){
return {acquisition(std::forward<Args>(args)...), {}};
}
The usage syntax then changes slightly, to
auto resource = make_c_handler<CStyleResource, releaseResource>(
acquireResource, "name", nullptr, 0);
make_c_shared_handler would not benefit from changing to a templated deleter, as shared_ptr does not carry deleter information available at compile time.π7
ImplementingCVs.pdf
151.9 KB
--
Implementing Condition Variables with Semaphores
The paper discusses the implementation in C# but it is a straightforward translation to C/C++.
The reason I shared this paper is to show the thought process and the things people have to keep in mind when designing a solution for concurrency/parallel programming primitives.
All the kernels provide a semaphore as a primitive that can be used to build all the necessary primitives that a concurrency library provides.
Implementing Condition Variables with Semaphores
The paper discusses the implementation in C# but it is a straightforward translation to C/C++.
The reason I shared this paper is to show the thought process and the things people have to keep in mind when designing a solution for concurrency/parallel programming primitives.
All the kernels provide a semaphore as a primitive that can be used to build all the necessary primitives that a concurrency library provides.
π6
Undefined Behavior Sanitizer for C/C++ explained in depth:
https://maskray.me/blog/2023-01-29-all-about-undefined-behavior-sanitizer
If you want to learn what Undefined Behavior is, you can refer to a post earlier in this channel here.
https://maskray.me/blog/2023-01-29-all-about-undefined-behavior-sanitizer
If you want to learn what Undefined Behavior is, you can refer to a post earlier in this channel here.
MaskRay
All about UndefinedBehaviorSanitizer
Updated in 2025-01. UndefinedBehaviorSanitizer (UBSan) is an undefined behavior detector for C/C++. It consists of code instrumentation and a runtime. Both components have multiple independent impleme
π9
C/C++ Resources and FAQ pinned Β«List of books recommended for learning C (Beginners to Advanced) Beginner: C Programming - A Modern Approach Effective C Beej's Guide to C Programming - Free Online Book Intermediate Modern C 21st Century C - C tips from new school Advanced Linuxβ¦Β»
The Missing Semester of Your CS Education is a practical and hands-on computer science class that fills the gaps in traditional CS education. This course covers essential topics often overlooked in standard curriculum, empowering students and programmers to work efficiently and effectively within the computing ecosystem. Topics include mastering the command shell for task automation, harnessing version control for collaboration and problem-solving, optimizing text editing with advanced features, managing remote machines seamlessly, streamlining file searching, data wrangling from the command line, leveraging virtual machines for experimentation, and enhancing security practices online. With 12 engaging lectures and practical exercises, this class equips participants with indispensable skills to excel in the world of computer science and programming.
https://www.youtube.com/@MissingSemester/
https://www.youtube.com/@MissingSemester/
π30π1