Smart pointers and RAII in the service of a programmer

Historically, management wants the task to be completed quickly. For this, programmers maintain the beauty and purity of the code. This post appeared as a reminder of the rarely used innovations in C ++ 11 - smart pointers that allow you to specify a functor to free resources.
For example, take the FILE file stream from stdio.h, which is loved for simplicity and speed, try to add beauty and a basic guarantee to it with exceptions:
unique_ptr my_file(fopen("test.txt", "w"), &fclose);
if(my_file)
  fwrite("test", 4, 1, my_file.get());

As a result, the code depends only on STL and requires a slight modification to file accesses, is written quickly, and looks modern. That's how it turned out RAII in its purest form.

How it works?


The fopen function returns a pointer to an object of type FILE, which is stored in the my_file variable along with a pointer to the fclose function. In this way, ownership of this file stream is transferred to a local variable.

When will the fclose function be called automatically?


  1. When leaving the scope of a variable (for example, from a function).
  2. If an exception occurs after creating my_file.
  3. When calling the assignment function to the object my_file.
  4. When calling my_file.reset ().

What is the overhead?


  1. The programmer needs to complicate the creation of the file, delete the fclose call and add to the unique_ptr <...> :: get () call all the file accesses.
  2. In the worst case, the compiler will need a memory cell to hold a pointer to the file delete function. At best, it will just put the fclose call in the right place for you, fully optimizing the my_file object.

What are the advantages of this approach?


  1. As with any smart pointer, you explicitly specify how you own the object. In this case, it is indicated that the object is not shared (unique_ptr).
  2. You can get rid of unnecessary copy-paste by declaring your type like this:
    typedef unique_ptr MyFileType;
  3. If you use a lot of files, it makes sense to write a small wrapper
    MyFileType MakeFile(const char* filename, const char* mode)
    {
      return unique_ptr(fopen(filename, mode), &fclose);
    }
    
    ... and use it like this:
    auto my_file = MakeFile("test.txt", "w");
  4. Allows you to get rid of writing extra code in the destructor. Why too much? You have already indicated to the compiler how you want to manage this resource, and now it is his job.
  5. You can use objects of type MyFileType in standard STL containers:
    vector my_files;
    my_files.push_back(MakeFile("test.txt", "w"));
    
    ... and do not waste your time monitoring the lifetime of objects. In C ++ 11 vector You can safely return from the function.

Here are some more ideas from the C Runtime Library:


Those who are puzzled or keen on optimizing for Windows know that access to aligned data is faster. So you can create a pointer to memory aligned to 16 bytes using the Microsoft Visual C Runtime library:
unique_ptr my_buffer((char*)(_aligned_malloc(512, 16)), &_aligned_free);
my_buffer[0] = ‘x’; //  использование буфера

Having written the template once:
template
unique_ptr
MakeAlignedBuffer(size_t element_count, size_t alignment = alignment_of::value)
{
	return unique_ptr
		(reinterpret_cast(_aligned_malloc(element_count*sizeof(T), alignment)), &_aligned_free);
}
you can forget about the errors of allocating and deleting memory with different functions (created through new [] in one module, deleted via delete in another).

But what if several objects own a specific WinAPI resource?


As an example, consider the situation when in a GUI application several different objects use functions that are in a dynamically loaded DLL. In this case, it is not so easy to program the timely unloading of the library as we would like:
Loading the library ...
auto my_module = shared_ptr(new HMODULE(LoadLibrary(_T("my_library.dll"))), [](HMODULE* instance){
	FreeLibrary(*instance);  //  выгружаем библиотеку когда ссылок на нее больше нет
});
Next, we distribute my_module to objects ...
module_owner1.set_module(my_module);
module_owner2.set_module(my_module);  //  или можем хоть в vector их сложить
In the object we use the necessary functions ...
if(my_module && *my_module)
{
	auto func1 = GetProcAddress(*my_module, "MyFunc");
}
When we stop using functions and the reference counter to the object becomes zero, the FreeLibrary function will be called and the object will be deleted.

How to use lambda function in unique_ptr?


You must use the function template like this:
auto my_instance = std::unique_ptr>
                    (new HMODULE(LoadLibrary(_T("my_library.dll"))), [](HMODULE* instance){ FreeLibrary(*instance); });

Conclusion


Dear readers, remember that any technology is developed for a specific purpose and should not be used where its use is not justified, i.e. Do not rush to replace all pointers with smart pointers without thinking about the need and without analyzing the consequences. These approaches also have disadvantages and they have been repeatedly discussed on the Habr. Be professional.
Thanks.

Also popular now: