C++: How to Utilize SBRM for C-style Interfaces and Resources

Previously I introduced the concept of scope-bound resource management, a very useful object-oriented concept available to us in C++.

Unfortunately, if you’re migrating from C to C++, you likely don’t have the luxury of converting all of your legacy code to modern C++. It would still be nice to utilize SBRM strategies with our C-style interfaces.

Taking a Look at C-style Resource Management

Let’s start by looking at the typical C-style resource management pattern. Many C libraries and interfaces utilize an initialization function, a stop/release/free function, and also require the caller to maintain a handle. For example, consider the fopen API:

FILE *fopen(const char *filename, const char *mode);

We supply fopen with a filename and mode, and in return we get a file handle (FILE*). This file handle is used by other file APIs. When we are done with our file, the handle must be passed to fclose.

As we saw in the SBRM article, this is the perfect type of operation for utilizing SBRM. Luckily we have quite a simple way to utilize SBRM for managing resources from a C-style interface: C++ smart pointers.

Let’s take a look at how to make smart pointers work for our C-style resources, starting with handling the cleanup of our resource.

Specifying Custom Deleters

Both std::unique_ptr and std::shared_ptr allow us to specify custom deleters. By using the custom deleter functionality, we can put our C-based resource under management by a smart pointer and rely on SBRM to handle cleanup for us.

Whenever the pointer goes out of scope (or the reference count goes to 0), the release function will be called on the managed pointer.

std::unique_ptr

In most cases, you will want to utilize a std::unique_ptr for your resources. A std::unique_ptr requires minimal overhead and indicates that the resource can only have one owner at a time.

std::unique_ptr calls delete by default. To put our C-objects under SBRM we will need to specify a custom deleter. For a std::unique_ptr, the type of the deleter must be specified as part of the pointer type.

Using our file example, we can simply create a std::unique_ptr to place our FILE resource under management and specify fclose as the deleter function. Once our pointer goes out of scope, fclose will be called on the file automatically.

std::unique_ptr< FILE, decltype(fclose)> 
	fptr( fopen("test.txt", "r"), fclose);

Any function can really be supplied as a deleter. Perhaps you need a more complex operation to handle the release of your resource, or maybe you just want to print something out for debug purposes. Easy peasy:

//lambda deleter fn
auto file_deleter = [](FILE * x) {
    printf("Closing file pointer %p\n", x);
    fclose(x);
};

std::unique_ptr< FILE, decltype(file_deleter)> 
	fptr(fopen("test.txt", "r"), file_deleter);

std::shared_ptr

If your resource needs to be shared by multiple consumers at one time, you will want to consider std::shared_ptr. Like std::unique_ptr, you can specify a custom deleter. Unlike std::unique_ptr, the deleter is not part of a std::shared_ptr’s type:

std::shared_ptr<FILE> fptr(fopen("test.txt", "r"), fclose);

When the std::shared_ptr reference count drops to 0, the custom deleter will be called on the managed pointer.

Providing a Resource Generation Function

It’s great that we can now utilize SBRM for our C-style resources, but this is certainly not a beautiful line of code:

std::unique_ptr< FILE, decltype(file_deleter)> fptr
	(fopen("test.txt", "r"), file_deleter);

Wouldn’t it be better to utilize an API like this one?

auto my_file = fopen_unique("test.txt", "r");

Luckily, we can create a simple wrapper function and file type to make life easier:

// Unique file type
typedef std::unique_ptr<FILE, decltype(&fclose)> ufile_t;

ufile_t fopen_unique(const char * fname, const char * mode)
{
	return {fopen(fname, mode), fclose};
}

We can also build a similar wrapper for creating our std::shared_ptr:

// Shared file type
typedef std::shared_ptr<FILE> sfile_t;

sfile_t fopen_shared(const char * fname, const char * mode)
{
	return {fopen(fname, mode), fclose};
}

A More Generic Approach Using Templates

Implementing a wrapper function to reduce C++ verbosity is a great strategy if you are only managing a single C-style resource. However, if we’re managing multiple resources, re-implementing wrappers for each type can become tedious. I’ve lifted a more generic approach from a StackOverflow article. The resource creation is a bit more verbose, but the approach is generic and reusable. Instead of using our fopen_unique approach above, we would open a file like this:

auto ufile = acquire_unique_resource<FILE>(fopen, fclose, filename, "w");

We’ll call our generic acquire_unique_resource method and template it on the type of resource we want to be managed (FILE). Then we supply the acquisition function (fopen) and the release function (fclose). Any additional arguments supplied to the function will be forwarded your acquisition function (filename and mode).

Let’s take a look at the variadic template function implementation:

template<typename T,
	typename AcquisitionFunc,
	typename ReleaseFunc,
	typename ...Args>
std::unique_ptr<T,ReleaseFunc> acquire_unique_resource(
	AcquisitionFunc acquire, ReleaseFunc release, Args&&...args)
{
	return {acquire(std::forward<Args>(args)...), release};
}

We can also create an acquire_shared_resource function. This function returns a std::shared_ptr and instead of a std::unique_ptr:

template<typename T,
	typename AcquisitionFunc,
	typename ReleaseFunc,
	typename ...Args>
std::shared_ptr<T> acquire_shared_resource(
	AcquisitionFunc acquire, ReleaseFunc release, Args&&...args)
{
	return {acquire(std::forward<Args>(args)...), release};
}

This pattern is usable for any C-style resource that utilizes an acquisition/initialization function and returns a pointer.

Putting It All Together

You can find a C-style SBRM example in the embedded-resources Github repository. Start using SBRM to manage your C-style resources!

Further Reading

Using C++ Without the Heap

Want to use C++, but worried about how much it relies on dynamic memory allocations? Our course provides a hands-on approach for learning a diverse set of patterns, tools, and techniques for writing C++ code that never uses the heap.

Learn More on the Course Page

Migrating from C to C++ Articles

Share Your Thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.