A site for solving at least some of your technical problems...
A site for solving at least some of your technical problems...
If you used C++ for a while, you may have come across a note saying that the shared_ptr<>() was not (always) safe to use with tracking resource.
The fact is that there is a trick to using a shared pointer.
The following code will look correct to most of us, after all, you create a resource then save it in a shared pointer which is going to automatically get rid of it on exceptions or when you return from your function. Great!
#include <memory> void deleter(FILE * f) { fclose(f); } ... FILE * f(fopen("/tmp/random", O_CREAT | ...)); if(f == nullptr) { ...handle error... } // here f is a potential leak liability std::shared_ptr<FILE, decltype(&deleter)> raii_file(f, deleter); // now f will always be close(f)'d
The problem of shared_ptr<>() is that is allocates memory so a bad_alloc exception can occur. So in the code above, if that bad_alloc (or other exception) occurs while attempting to create the raii_file, you will leak that FILE object.
Here are two solutions.
In many cases, it will be easy enough to convert to a unique_ptr<>(). After all, that resolves the issue as is because a unique_ptr<>() constructor can't throw an exception (outside of a stack overflow, I suppose):
FILE * f(fopen("/tmp/random", O_CREAT | ...)); if(f == nullptr) { ...handle error... } std::unique_ptr<FILE, decltype(&deleter)> raii_file(f, deleter);
There are major reasons why this often doesn't work.
If you have to share the pointer with other functions (except maybe internal lambdas), you'll want to use shared_ptr<>(). Keep in mind that you can't share a unique_ptr<>(). So if the f parameter is only used in your current function, you'll be just fine. If you have to share it all over the place, that's not going to be practical at all.
Remember that copying a resource from one unique_ptr<>() to another does a transfer of the pointer. In other words, the old unique_ptr<>() becomes a null pointer and the new one is responsible for deleting the resource.
Another major reason for not using a unique_ptr<>() is because you end up returning the pointer. Again, the fact that the copy of a unique_ptr<>() doesn't do a copy but a transfer, it doesn't work well in a return statement.
So... is there a way to make my first example work safely?
The problem we mentioned is that the constructor is going to allocate memory. This is actually try in all situations, for example, doing the following is not safe either:
std::shared_ptr<my_type_t> obj(new my_type_t);
Your expression between the parenthesis allocates an object.
Then the shared_ptr<>() attempts an allocation and that allocation fails. It throws with a bad_alloc. The object you just allocated is leaked.
For a simple allocation, the shared_ptr<>() implementation offers a simple fix. You can use the make_shared<>() instead. So the allocation above is changed to:
std::shared_ptr<my_type_t> obj(std::make_shared<my_type_t>());
When you do an allocation for a resource other than just a memory buffer for an object, the std::make_shared<>() can't be used.
Note: You can pass arguments to your my_type_t constructors. C++20 also adds the ability to allocate an array instead of just one instance of an object.
The solution is to pre-create the shared pointer and then use the reset() function. This is because the reset() function knows to delete the object if the allocation fails (assuming an allocation is required.)
So the following code looks very similar to my first example, but it is safe in all cases:
#include <memory> ... std::shared_ptr<FILE, decltype(&deleter)> raii_file; FILE * f(fopen("/tmp/random", O_CREAT | ...)); if(f == nullptr) { ...handle error... } raii_file.reset(f, deleter);
When the reset() needs to allocate a memory buffer and that somehow fails, the function automatically calls your deleter. So something like this happens:
reset(p)
{
try
{
ptr = new some_class; // allocation happens here
ptr.count = 1; // success!
ptr.data = p;
}
catch(...) // failure...
{
deleter(p); // get rid of the resource
throw; // propagate the exception
}
}
My example is extremely simplified. They use an allocator, the function first checks whether a some_class object already exists, etc. But it shows the principal which shows how the resource will safely be removed when the reset() throws.
More reading: RAII Generic Deleter