Raii c что это

от admin

RAII и необрабатываемые исключения

Наверняка все знают прописную (в книгах про С++) истину о чудесной методологии RAII, если нет — приведу краткое описание из википедии.

Получение ресурса есть инициализация (англ. Resource Acquisition Is Initialization (RAII)) — программная идиома объектно-ориентированного программирования, смысл которой заключается в том, что с помощью тех или иных программных механизмов получение некоторого ресурса неразрывно совмещается с инициализацией, а освобождение — с уничтожением объекта.

Типичным (хотя и не единственным) способом реализации является организация получения доступа к ресурсу в конструкторе, а освобождения — в деструкторе соответствующего класса. Поскольку деструктор автоматической переменной вызывается при выходе её из области видимости, то ресурс гарантированно освобождается при уничтожении переменной. Это справедливо и в ситуациях, в которых возникают исключения. Это делает RAII ключевой концепцией для написания безопасного при исключениях кода в языках программирования, где конструкторы и деструкторы автоматических объектов вызываются автоматически, прежде всего — в C++.

Последнее предложение вроде как обещает 100% гарантию результата, но как всегда в жизни, а особенно в С++, есть нюанс.

Пример кода использующего RAII:

Допустим, есть какой-то класс, инкапсулирующий доступ к сети:

Создаём класс, который будет реализовывать RAII:

Теперь в функции main мы можем безопасно использовать этот ресурс:

Вроде бы всё нормально, как обещает RAII, даже если будет сгенерировано исключение, указатель m_net в классе LockNet будет корректно удалён. Правильно?

Почему-то в описании RAII обычно забывают написать, что для работы этой техники исключение ОБЯЗАНО быть перехвачено обработчиком исключений этого типа, иначе, если обработчик не будет найден, будет вызвана std::terminate(), которая аварийно завершит выполнение программы. Страуструп описывает это в книге «Язык программирования С++ (03)», глава 14.7.

Удаление локальных объектов зависит от реализации, где-то они будут удалены, где-то наоборот, чтобы разработчик мог увидеть состояние локальных объектов на момент исключения, в дебагере когда загрузит coredump. И рекомендует если вам нужно гарантированное удаление локальных объектов оборачивать код в функции main блоком try — catch (. ), который перехватывает любые исключения.

Т.ч. в коде функции main, если будет исключение до оператора return 0;, мы получаем обычную утечку ресурсов.
Она не фатальная, так как ОС сохранит coredump и освободит ресурсы, занятые программой.

Как в этом убедиться? Пишем проверочный код!

В данном коде мы используем умные указатели, которые реализовывают технику RAII:

Скомпилировав и запустив эту программу, получаем вывод:

Переписываем функцию main, заворачивая вызов функции генерирующей исключение в try — catch блок:

И, вуаля — всё начинает работать как и должно.

Вызываются не только конструкторы, но и деструкторы объектов, хранящихся в умных указателях.

Тем не менее, всё согласно стандарту:

15.2 Constructors and destructors

1. As control passes from a throw-expression to a handler, destructors are invoked for all automatic objects
constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the
completion of their construction.

3. The process of calling destructors for automatic objects constructed on the path from a try block to a
throw-expression is called “stack unwinding.” If a destructor called during stack unwinding exits with an
exception, std::terminate is called (15.5.1).

15.3 Handling an exception

9 If no matching handler is found, the function std::terminate() is called; whether or not the stack is
unwound before this call to std::terminate() is implementation-defined (15.5.1).

Classic RAII

RAII (Resource Acquisition Is Initialization) is a difficult to say name that does not do justice to one of the most useful programming idioms. This article describes the classic way of using RAII, with the full example of the copy file example rewritten.

Introduction

RAII is a resource management technique developed in C++ by Bjarne Stroustrup and Andrew Koenig in the 1980s. It largely eliminates the need to collect garbage, by not creating garbage in the first place and providing ease of use and correctness when dealing with resources (e.g. memory, files, etc.).

In this article I’ll describe what I call the classical RAII. As we’ll see later, there are some minor variations.

RAII fundamentally works by wrapping a resource in an object, initializing the resource in the constructor. If the constructor succeeds, the object can be used and the destructor will automatically cleanup the resource. If the constructor fails, it throws an exception, the object can’t be used and conveniently the destructor is not called.

When opening a file with fopen , the resource that needs to be wrapped is a FILE * . In the constructor we need to open the file. If opening the file succeeds, the destructor will call fclose . If opening a file fails (e.g. because of the file permissions) the constructor throws an exception and the destructor is not called.

A class to wrap the FILE * using a classical RAII idiom would look like:

Notice that the construction has two possible outcomes: the constructor succeeds or it throws.

If fopen succeeds, then the constructor succeeds and the instance has a f_ that is not null. f_ can be used in other file class methods without the need to test for null, including in the destructor. This behaviour explains the complicated RAII name (Resource Acquisition Is Initialization): when the resource is acquired, the object is fully initialized.

If fopen fails, the constructor throws: the object is not constructed, the user can’t call methods and the destructor is not called.

Advantages

First of all notice how fclose can be arranged to be placed close to the matching fopen (locality), they are no longer getting further away as non-related code gets added.

Secondly notice how the file class encapsulates the logic of managing the FILE * . To open and use two files, one needs to construct them like this:

On the happy path this will create two file instances, each in charge with its own FILE * . When they go out of scope they will each be destructed, closing each file, in the reverse order of the declaration: src is created first, and destructed last. The scope of dst is surrounded by the scope of dst .

If say the creation of dst fails, then the execution exits the scope, ensuring that the destructor if src is called closing its already opened FILE * . This provides exception safety for the resources on the stack.

Issues

No real issues, more like things to pay attention to:

  • Ensure the destructors don’t throw
  • Pay attention to the copy constructor and assignment operator. One easy option is to delete them to ensure that destructor does not try to release twice the same resource.

Note: Strictly speaking the std::vector would initialize to 0 which is additional work comparing with just allocating memory like the C example.

What is meant by Resource Acquisition is Initialization (RAII)?

What is meant by Resource Acquisition is Initialization (RAII)?

10 Answers 10

It’s a really terrible name for an incredibly powerful concept, and perhaps one of the number 1 things that C++ developers miss when they switch to other languages. There has been a bit of a movement to try to rename this concept as Scope-Bound Resource Management, though it doesn’t seem to have caught on just yet.

When we say ‘Resource’ we don’t just mean memory — it could be file handles, network sockets, database handles, GDI objects. In short, things that we have a finite supply of and so we need to be able to control their usage. The ‘Scope-bound’ aspect means that the lifetime of the object is bound to the scope of a variable, so when the variable goes out of scope then the destructor will release the resource. A very useful property of this is that it makes for greater exception-safety. For instance, compare this:

With the RAII one

In this latter case, when the exception is thrown and the stack is unwound, the local variables are destroyed which ensures that our resource is cleaned up and doesn’t leak.

Читать:
Как войти windows 7 если ошибка 0x0000006b

This is a programming idiom which briefly means that you

  • encapsulate a resource into a class (whose constructor usually — but not necessarily** — acquires the resource, and its destructor always releases it)
  • use the resource via a local instance of the class*
  • the resource is automatically freed when the object gets out of scope

This guarantees that whatever happens while the resource is in use, it will eventually get freed (whether due to normal return, destruction of the containing object, or an exception thrown).

It is a widely used good practice in C++, because apart from being a safe way to deal with resources, it also makes your code much cleaner as you don’t need to mix error handling code with the main functionality.

* Update: «local» may mean a local variable, or a nonstatic member variable of a class. In the latter case the member variable is initialized and destroyed with its owner object.

** Update2: as @sbi pointed out, the resource — although often is allocated inside the constructor — may also be allocated outside and passed in as a parameter.

«RAII» stands for «Resource Acquisition is Initialization» and is actually quite a misnomer, since it isn’t resource acquisition (and the initialization of an object) it is concerned with, but releasing the resource (by means of destruction of an object).
But RAII is the name we got and it sticks.

At its very heart, the idiom features encapsulating resources (chunks of memory, open files, unlocked mutexes, you-name-it) in local, automatic objects, and having the destructor of that object releasing the resource when the object is destroyed at the end of the scope it belongs to:

Of course, objects aren’t always local, automatic objects. They could be members of a class, too:

If such objects manage memory, they are often called «smart pointers».

There are many variations of this. For example, in the first code snippets the question arises what would happen if someone wanted to copy obj . The easiest way out would be to simply disallow copying. std::unique_ptr<> , a smart pointer to be part of the standard library as featured by the next C++ standard, does this.
Another such smart pointer, std::shared_ptr features «shared ownership» of the resource (a dynamically allocated object) it holds. That is, it can freely be copied and all copies refer to the same object. The smart pointer keeps track of how many copies refer to the same object and will delete it when the last one is being destroyed.
A third variant is featured by std::auto_ptr which implements a kind of move-semantics: An object is owned by only one pointer, and attempting to copy an object will result (through syntax hackery) in transferring ownership of the object to the target of the copy operation.

Ry-'s user avatar

An object’s lifetime is determined by its scope. However, sometimes we need, or it is useful, to create an object that lives independently of the scope where it was created. In C++, the operator new is used to create such an object. And to destroy the object, the operator delete can be used. Objects created by the operator new are dynamically allocated, i.e. allocated in dynamic memory (also called heap or free store). So, an object that was created by new will continue to exist until it’s explicitly destroyed using delete .

Some mistakes that can occur when using new and delete are:

  • Leaked object (or memory): using new to allocate an object and forget to delete the object.
  • Premature delete (or dangling reference): holding another pointer to an object, delete the object, and then use the other pointer.
  • Double delete: trying to delete an object twice.

Generally, scoped variables are preferred. However, RAII can be used as an alternative to new and delete to make an object live independently of its scope. Such a technique consists of taking the pointer to the object that was allocated on the heap and placing it in a handle/manager object. The latter has a destructor that will take care of destroying the object. This will guarantee that the object is available to any function that wants access to it, and that the object is destroyed when the lifetime of the handle object ends, without the need for explicit cleanup.

Examples from the C++ standard library that use RAII are std::string and std::vector .

Consider this piece of code:

when you create a vector and you push elements to it, you don’t care about allocating and deallocating such elements. The vector uses new to allocate space for its elements on the heap, and delete to free that space. You as a user of vector you don’t care about the implementation details and will trust vector not to leak. In this case, the vector is the handle object of its elements.

Other examples from the standard library that use RAII are std::shared_ptr , std::unique_ptr , and std::lock_guard .

Another name for this technique is SBRM, short for Scope-Bound Resource Management.

Raii c что это

Resource Acquisition Is Initialization or RAII, is a C++ programming technique [1] [2] which binds the life cycle of a resource that must be acquired before use (allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply) to the lifetime of an object.

RAII guarantees that the resource is available to any function that may access the object (resource availability is a class invariant, eliminating redundant runtime tests). It also guarantees that all resources are released when the lifetime of their controlling object ends, in reverse order of acquisition. Likewise, if resource acquisition fails (the constructor exits with an exception), all resources acquired by every fully-constructed member and base subobject are released in reverse order of initialization. This leverages the core language features (object lifetime, scope exit, order of initialization and stack unwinding) to eliminate resource leaks and guarantee exception safety. Another name for this technique is Scope-Bound Resource Management (SBRM), after the basic use case where the lifetime of an RAII object ends due to scope exit.

RAII can be summarized as follows:

  • encapsulate each resource into a class, where
  • the constructor acquires the resource and establishes all class invariants or throws an exception if that cannot be done,
  • the destructor releases the resource and never throws exceptions;
  • always use the resource via an instance of a RAII-class that either
  • has automatic storage duration or temporary lifetime itself, or
  • has lifetime that is bounded by the lifetime of an automatic or temporary object

Move semantics make it possible to safely transfer resource ownership between objects, across scopes, and in and out of threads, while maintaining resource safety.

Classes with open() / close() , lock() / unlock() , or init() / copyFrom() / destroy() member functions are typical examples of non-RAII classes:

[edit] The standard library

The C++ library classes that manage their own resources follow RAII: std::string , std::vector , std::jthread (since C++20) , and many others acquire their resources in constructors (which throw exceptions on errors), release them in their destructors (which never throw), and don’t require explicit cleanup.

In addition, the standard library offers several RAII wrappers to manage user-provided resources:

  • std::unique_ptr and std::shared_ptr to manage dynamically-allocated memory or, with a user-provided deleter, any resource represented by a plain pointer;
  • std::lock_guard , std::unique_lock , std::shared_lock to manage mutexes.

[edit] Notes

RAII does not apply to the management of the resources that are not acquired before use: CPU time, cores, and cache capacity, entropy pool capacity, network bandwidth, electric power consumption, stack memory.

Похожие статьи