C/C++ Resources and FAQ
1.46K subscribers
1 photo
2 files
23 links
This is a sub channel for the C/C++ forum (https://t.me/programminginc). Here you can find links to various useful resources (including book recommendations and video resources) for C/C++ programmers and also answers to frequently asked C/C++ questions.
Download Telegram
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:
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 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.
👍6
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/
👍30👎1