Manage your resources so strong in C + + PDF Print E-mail
User Rating: / 0
PoorBest 
Tuesday, 20 May 2008

Introduction
Take the case of a resource any manipulated through the class Resource, whose goal is to represent this resource point of view of language and allow its handling in C + +. It could be a network connection, for example. Our class resource called Socket then certainly not Resource.

Now imagine that you had to control the number of instances of this class Resource. In our example with the Socket class, it might be to limit the number of network connections simultaneously open. You'll then with the constraint of having to count the number of instances of Resource who are still in use. At first glance, this seems childish. But as always, and even more in C + +, this is not because there are many ways to do all they are worth. Let us therefore to an early solution.

Classical approach
Under this article, I decided to centralize the operations of creating and controlling the number of instances of Resource within a specialized manager called ResourceManager.

This class ResourceManager tantamount to a de facto factory, and has a member function New () to instantiate or not a new resource, and to keep track if it is never the case. The aim is to know at any moment the number of instances in use, to limit their number. The maximum number of instances is provided as a parameter to the constructor ResourceManager.
ResourceManager.h
Resource class;
typedef Resource * ResourcePtr;

ResourceManager class
(
public:
     / / Sets the maximum number of Resource simultaneously
     ResourceManager (int MaxNbInstances);
    
     / / Returns a new Resource it's less than
     / / MaxNbInstances in use, NULL otherwise.
     New ResourcePtr ();
);

If this interface design public use of ResourceManager is one thing, implementing it is another. For a problem arises: how to know the number of bodies set (easy) and still in use (a little less easy)? In other words, how to be informed in ResourceManager an object allocated via New () has been destroyed?

One solution, common in procedural programming, is to provide a pair of New function, which was responsible for the destruction of forums:
ResourceManager.h
Resource class;
typedef Resource * ResourcePtr;

ResourceManager class
(
public:
     / / Sets the maximum number of Resource simultaneously
     ResourceManager (int MaxNbInstances);
    
     / / Returns a new Resource it's less than
     / / MaxNbInstances in use, NULL otherwise.
     New ResourcePtr ();

     / / Delete a Resource allocation with New ()
     void Delete (ResourcePtr);
    
private:
     / / Number of instances in use
     int NbInstances;
     / / Maximum number of bodies authorised
     const int MaxNbInstances;
);

ResourceManager.cpp
# include "ResourceManager.h"
# include "Resource.h"

ResourceManager: ResourceManager (int Max):
     NbInstances (0),
     MaxNbInstances (Max)
(
)

ResourcePtr ResourceManager: New ()
(
     if (NbInstances <MaxNbInstances)
     (
         ResourcePtr r = new Resource ();
         / / On increments NbInstances _après_ be allocated
         / / Resource successfully to allow our
         / / Manager in a coherent state if an exception
         / / Was closed during the creation of an object Resource
         + + this-> NbInstances;
         return r;
     )
     return 0; / / NULL
)

void ResourceManager: Delete (ResourcePtr Res)
(
     If (Res)
     (
         -- this-> NbInstances;
         delete Res;
     )
)

Like ResourceManager: New () is responsible for instantiate objects of type Resource, ResourceManager: Delete () is responsible for destroying them. One could consider entrusting more work to these functions, such as maintaining a list of instances allocated, but I preferred to keep things as simple as possible.

Note the order of operations within ResourceManager: New (): Resource object is first allocated, then the meter NbInstances is updated, and especially not the reverse. For if the allocation of New Resource were to fail (because of lack of memory for example) and that an exception was lifted (std:: bad_alloc), the object ResourceManager then be left in an incoherent state: an object Additional Resource be recorded as being used while it does not exist.

The problem with this approach
This concept of twin functions based on a principle of programming most holy: It is he who has allocated an object that would destroy it. ResourceManager and is responsible for the creation but also the destruction of objects Resource through its torque functions New members () and Delete ().

If this principle is very holy, its implementation in this first attempt the east a little less, and is not really what is best in C + +. In introducing the function Delete () in the public interface of ResourceManager, we added a size constraint on the use of the New function (): pointer that returns must be systematically and released through Delete ().

While in some languages such as C, it is not already an obvious constraint to meet in any circumstance (an oversight is so fast arrived), it is particularly complex in C + + because of the support exceptions through language .
/ / Any function that uses an object Resource
void UseResource (ResourcePtr);

Void Test ()
(
     / / Maximum allowed 3 forums
     ResourceManager mgr (3);

     ResourcePtr r = mgr.New ();
     if (r)
     (
         UseResource (r);
         mgr.Delete (r);
     )
)

In the above example, if an exception is raised by the function UseResource (), the flow of classical performance is interrupted and the pointer returned by r ResourceManager: New () is never released. In other words, the resource is permanently lost!

Many objects languages (Java, C #, Python, PHP, ...) to remedy this problem using the keyword finally, but C + + is not. But it is possible to approach his philosophy of use through the construction following example:
/ / Any function that uses an object Resource
void UseResource (ResourcePtr);

Void Test ()
(
     / / Maximum allowed 3 forums
     ResourceManager mgr (3);

     ResourcePtr r = mgr.New ();
     if (r)
     (
         try
         (
             UseResource (r);
         )
         catch (...) / / pseudo finally block
         (
             mgr.Delete (r);
             throw / / revive the exception
         )
         mgr.Delete (r);
     )
)

As seen above, manage resources in this way is extremely heavy. Moreover, such as C + + does not collect automatically memory unlike languages offering keyword finally, such a construction would be used far more frequently than in these other languages, which would make the code simply illegible, and encourage the programmer to neglect this kind of "details". The use of the construction finally try ... is therefore not an appropriate response to a language like C + +.

Moreover, given as much work as a programmer client to ensure that our management objects to be valid is the sign of a very poor strength from our class ResourceManager. Indeed: a too basic use of this class is enough to jeopardise its reliability, and it is the programmer to ensure customer to maintain the stability of our system! But the C + + is reputed to allow writing programs robust. Yes, but how?

The approach C + +: RAII
The answer widespread C + + to the problem of resource management (and not just the particular case of memory management) called RAII. It is the acronym for Resource Acquisition Is Initialization, which can be translated as acquisition of resources during initialization. Actually, il ne faut pas trop focus within the meaning of this acronym, insofar as it does not reflect fully the concept he describes. The definition of the FAQ C + + said: This is a programming language to manipulate any resource (memory, file, mutex connection to a database, ...) through a local variable that will acquire this resource at its initialization and free at its destruction.

This idiom derives in fact benefit of a specific language C + +: the presence of a destructive class deterministic, in other words a function that is automatically and systematically executed when an object is destroyed. The principle of RAII is therefore to use this parity between the manufacturer and destructive to implement, the sauce C + + this time, the concept presented earlier: It is he who has allocated an object that would destroy it.

Reminder: the only case where the destroyer of an object is not known when the manufacturer has been is that the latter has removed an exception. Indeed, if a manufacturer raises an exception, the object is not considered to be constructed, and hence its destructiveness is not executed. Be vigilant on this point with the resources that you allocate in the constructor and free in the destructive: you may leak if an exception was closed shortly after their allowance while you are still in the constructor.

Presented otherwise, the RAII is to materialize a resource through which an object acquires this resource at its initialization, and ensure that it is to release its destruction, even if the programmer has not thought of. The manufacturer is therefore act according Init () and the destructive function Free (). The following example implements a class Resource traditionally managed and another class ResourceRAII whose design respects the concepts of RAII.
Resource class
(
public:
     / / Resource allocation: should be called
     / / First, and only once!
     bool Init ();
     / / Release of resources: not
     / / Forget to call once done!
     Void Free ();
    
     Void DoSomething ();
);

class RessourceRAII
(
public:
     RessourceRAII ();
     ~ RessourceRAII ();
    
     Void DoSomething ();
);

Void TestRessource ()
(
     Resource r;
     if (r.Init ())
     (
         try
         (
             r.DoSomething ();
         )
         catch (...) / / pseudo finally
         (
             r.Free ();
             Throw / / relaunch
         )
         r.Free ();
     )
     Else
     (
         / / Hours ... what to do, what's the problem?
     )
)

Void TestRessourceRAII ()
(
     RessourceRAII r / / exception waived if failure
     r.DoSomething ();
)

This example illustrates fairly well the vast simplification of use and robustness brought by the RAII. Because the principle of RAII wants as often as, if the object has been successfully established, then it is directly usable. If it fails to be, it can raise an exception to cancel its construction. The idea behind this behavior is: What would serve me an object that has not been able to build and is not therefore not usable? But of course, there are cases (as std:: ifstream for example).

Let's see now how to solve our problem initial objects Resource Management in addressing under this new angle.

Implementation: first test

Introducing shared_ptr
The most simple and economical to implement the RAII is certainly to use smart pointers. Pointers intelligent are the quintessential example of practical application of this idiom. They marry with the concept of genericity templates, which allows implementation of RAII fast and cheaply. In fact, intelligent pointers are indispensable companions of any C + + programmer seriously.

There are many different implementations. I chose shared_ptr historically from the library boost, and now a member of TR1. This means that shared_ptr is being adopted within the standard of language and will therefore ultimately available as standard with most compilers.

Pending this time in the future, it is clear that the present state of things (July 2007) is different. Only GCC proposes std:: tr1:: shared_ptr standard, and again, not in all platforms. But things are not so negative that it neither, since it is a very mature implementation boost:: shared_ptr, and there is also Boost.TR1 which provides header files redefining this type (and than others) within the space repository std:: tr1. More specifically, it is perfectly possible to use the type std:: tr1:: shared_ptr as defined in the Technical Report 1 with most compilers provided to install Boost.TR1. In this connection, you can read Install and use Boost/Boost.TR1 with Visual C + +.

First test
Take the example initial and adapt it for use std:: tr1:: shared_ptr:
ResourceManager.h
# include <tr1/memory>

Resource class;
typedef std:: tr1:: shared_ptr <Resource> ResourcePtr;

ResourceManager class
(
public:
     / / Sets the maximum number of Resource simultaneously
     ResourceManager (int MaxNbInstances);
    
     / / Returns a new Resource it's less than
     / / MaxNbInstances in use, NULL otherwise.
     New ResourcePtr ();
        
private:
     / / Number of instances in use
     int NbInstances;
     / / Maximum number of bodies authorised
     const int MaxNbInstances;
);

Vis a vis the programmer customer, our ResourceManager could not be simpler and more reliable to use: it allocates a new resource via function ResourceManager: New (), it is used without asking any question, and once that 'they are finished with it, ResourceManager is automatically informed and updated a resource has been released.

This is attractive, but there is still a code the ResourceManager is automatically informed and updated.

The first solution that comes to mind is to modify the behaviour of Resource for the class inform his manager that it was destroyed.
Resource.h
ResourceManager class;

Resource class
(
public:
     / / = NULL pointer unaffiliated with a ResourceManager
     Resource (ResourceManager * = 0);
    
     / / Notifies the ResourceManager committed to its destruction
     ~ Resource ();
    
private:
     / / ResourceManager committed, can be NULL
     ResourceManager * Manager;
);

ResourceManager.h
# include <tr1/memory>

Resource class;
typedef std:: tr1:: shared_ptr <Resource> ResourcePtr;

ResourceManager class
(
public:
     / / Sets the maximum number of Resource simultaneously
     ResourceManager (int MaxNbInstances);
    
     / / Returns a new Resource it's less than
     / / MaxNbInstances in use, NULL otherwise.
     New ResourcePtr ();
        
private:
     Resource friend class;
     / / Called by objects Resources at their destruction
     void ResourceDestroyed (Resource *);

private:
     / / Number of instances in use
     int NbInstances;
     / / Maximum number of bodies authorised
     const int MaxNbInstances;
);

Resource.cpp
# include "Resource.h"
# include "ResourceManager.h"

Resource: Resource (ResourceManager * Bishop):
     Manager (Bishop)
(
)

Resource:: ~ Resource ()
(
     if (this-> Manager)
     (
         this-> Manager-> ResourceDestroyed (this);
     )
)

ResourceManager.cpp
# include "ResourceManager.h"
# include "Resource.h"

ResourceManager: ResourceManager (int Max):
     NbInstances (0),
     MaxNbInstances (Max)
(
)

ResourcePtr ResourceManager: New ()
(
     if (NbInstances <MaxNbInstances)
     (
         ResourcePtr r (new Resource ());
         / / On increments NbInstances _après_ be allocated
         / / Resource successfully to allow our
         / / Manager in a coherent state if an exception
         / / Was closed during the creation of an object Resource
         + + this-> NbInstances;
         return r;
     )
     ResourcePtr return () / / NULL
)

void ResourceManager: ResourceDestroyed (Resource *)
(
     -- NbInstances;
)


New problems
This solution works, but poses new problems:

The modification of the class Resource. This is not always possible. It could very well be a class after a third library. In this case, it would then develop an additional proxy class, which is typically a constraint which we like to happen.
The cross-reference between Resource and ResourceManager. Often a sign of bad design, cross-referencing increases the coupling between classes involved, which complicates their maintenance. And in this case, it poses the additional problem that, conceptually, it should not be. Indeed, if one spot from the viewpoint of the class Resource, was what to do with whether it is managed through a ResourceManager or not. And yet, this information that does not concern us just parasitize our design, then it falls to detail implementation of ResourceManager!
The use of keyword friend is also often an indication of bad design. The keyword itself is a good thing because it helps strengthen the encapsulation of ResourceManager making its member function ResourceDestroyed () private. Without him we would make the public, which would have been worse. But he does that in our case, it reflects the needs of class that we manage to secure itself its own management, then it is the only raison d'être of ResourceManager.
This solution is hardly generalized form of a class ResourceManager template for example.

New test: exploring the possibilities of shared_ptr
Our new goal is to achieve implementation of the RAII without having to modify Resource and without introducing cross-reference vis-à-vis ResourceManager. How?

If one thinks for a moment, our problem is to be informed of the destruction of an object Resource that has been allocated ourselves a little earlier. Ask this object of us when this happens the first was a possibility, but we saw that it contained many gaps.

When one finds oneself confronted with a situation of dependency circular, the solution to get out is often to introduce a third actor. And we have a third player: shared_ptr. Its use is almost unnoticed as discreet, but the fact remains that we have introduced a proxy on the class Resource. And the advantage of using shared_ptr is that this class has many advanced features.

In particular, shared_ptr has a very interesting manufacturer, agreeing to a second parameter deallocator:
template <class Y, class D>
shared_ptr (Y * p, D d);

The primary role of a deallocator is to free the memory allocated to the instance of the object which the pointer is encapsulated. The implementation default makes a simple call to delete. But as stated in the documentation shared_ptr, the concept of deallocator opens the door to other types of uses:

Custom deallocators allow a factory function returning a shared_ptr to insulate the user from its memory allocation strategy. Since the deallocator is not part of the type, changing the allocation strategy does not break source or binary compatibility, and does not require a client recompilation. For example, a "no-op" deallocator is useful when returning a shared_ptr to a statically allocated object, and other variations allow a shared_ptr to be used as a wrapper for another smart pointer, easing interoperability.

In our case, we will go one step further and use the deallocator as callback function performed at the destruction of an object Resource, a bit like a second destructive sum!
ResourceManager.h
# include <tr1/memory>

Resource class;
typedef std:: tr1:: shared_ptr <Resource> ResourcePtr;

ResourceManager class
(
public:
     / / Sets the maximum number of Resource simultaneously
     ResourceManager (int MaxNbInstances);
    
     / / Returns a new Resource it's less than
     / / MaxNbInstances in use, NULL otherwise.
     New ResourcePtr ();
        
private:
     ResourceDeleter class / / nested class
     ResourceDeleter friend;

private:
     / / Number of instances in use
     int NbInstances;
     / / Maximum number of bodies authorised
     const int MaxNbInstances;
);

ResourceManager.cpp
# include "ResourceManager.h"
# include "Resource.h"

/ / Class private responsible for destroying objects and Resource
/ / Update the meter NbInstances of ResourceManager
/ / (Used as deleter personalized shared_ptr)
ResourceManager class: ResourceDeleter
(
public:
     ResourceDeleter (ResourceManager * Bishop):
         Manager (Bishop)
     (
     )
    
     void operator () (* Resource Res)
     (
         -- (this-> Manager-> NbInstances);
         delete Res;
     )
    
private:
     ResourceManager * Manager;
);

ResourceManager: ResourceManager (int Max):
     NbInstances (0),
     MaxNbInstances (Max)
(
)

ResourcePtr ResourceManager: New ()
(
     if (NbInstances <MaxNbInstances)
     (
         ResourcePtr r (new Resource (), ResourceDeleter (this));
         / / On increments NbInstances _après_ be allocated
         / / Resource successfully to allow our
         / / Manager in a coherent state if an exception
         / / Was closed during the creation of an object Resource
         + + this-> NbInstances;
         return r;
     )
     ResourcePtr return () / / NULL
)

Elegant is not it? It is no longer necessary to amend Resource (which can be a class issue a code that you do not control), and cross-reference has disappeared. While it remains the use of keyword friend, but the door this time on a class embedded private ResourceManager, and is therefore not shocking. ResourceDeleter is indeed part of the implementation of ResourceManager, and the two classes therefore evolve together in a natural way.

Final Version
However, there are people like me who are really reluctant to use the keyword friend. Indeed, I interpret his presence as a sign of a possible defect in which one tries to limit the scope, instead of the correct. And in this case, the default is to include in the interface ResourceManager details of implementation, called ResourceDeleter. It is a defect questionable, insofar as it is the private interface of the class, and that C + + imposes make it visible in the header file. But as far as I'm concerned, I like this reduce private interface exposed at least to the extent possible of course. For if the user does not have the right nor the possibility of using ResourceDeleter, why inform him of his existence?

And in this case, it is possible to remove this information from the header file by partitioning ResourceManager in a namespace anonymous file implementation:
ResourceManager.h
# include <tr1/memory>
# include <boost/utility.hpp>

Resource class;
typedef std:: tr1:: shared_ptr <Resource> ResourcePtr;

ResourceManager class: public boost: noncopyable
(
public:
     / / Sets the maximum number of Resource simultaneously
     ResourceManager (int MaxNbInstances);
        
     / / Returns a new Resource it's less than
     / / MaxNbInstances in use, NULL otherwise.
     New ResourcePtr ();

     / / Returns the number of objects Resource in use
     int GetNbInstances () const;
        
private:
     / / Number of instances in use
     int NbInstances;
     / / Maximum number of bodies authorised
     const int MaxNbInstances;
);

ResourceManager.cpp
# include "ResourceManager.h"
# include "Resource.h"

namespace / / anonymous
(
     / / Class responsible for destroying objects and Resource
     / / Update the meter NbInstances of ResourceManager
     / / (Used as deleter personalized shared_ptr)
     class ResourceDeleter
     (
     public:
         ResourceDeleter (int * MgrNbInstances):
             Manager_NbInstances (MgrNbInstances)
         (
         )
        
         void operator () (* Resource Res)
         (
             -- (* this-> Manager_NbInstances);
             delete Res;
         )
        
     private:
         / / Pointer to ResourceManager: NbInstances
         int * Manager_NbInstances;
     );
)

ResourceManager: ResourceManager (int Max):
     NbInstances (0),
     MaxNbInstances (Max)
(
)

ResourcePtr ResourceManager: New ()
(
     if (NbInstances <MaxNbInstances)
     (
         ResourcePtr r (new Resource (), ResourceDeleter (& this-> NbInstances));
         / / On increments NbInstances _après_ be allocated
         / / Resource successfully to allow our
         / / Manager in a coherent state if an exception
         / / Was closed during the creation of an object Resource
         + + this-> NbInstances;
         return r;
     )
     ResourcePtr return () / / NULL
)

int ResourceManager: GetNbInstances () const
(
     return this-> NbInstances;
)

To provide access to data member private ResourceManager without introducing friend, I am compelled to use a pointer. I'm usually not very adept at this sort of acrobatics in fact designed to circumvent the access control of the compiler. But when applied in this case it seems to me acceptable.

Some might have used a reference to the place of a pointer. I chose a pointer because I think it makes more explicit that the class will change the ResourceDeleter int received as a parameter. But you are free to use a reference if you prefer.

Finally, you will notice the addition of a member function GetNbInstances () and the inclusion of boost / utility.hpp in order to derive ResourceManager to boost:: noncopyable. This helps protect our manager a copy accidental, and indicate a more telling that the class is not made for copied (the usual means to operate is to declare private, without implement, the manufacturer by recopy and the operator assignments). It also allows us, with Visual C + +, to remove the warning in 4512 (level 4): 'ResourceManager': operator assignment could not be generated.

Test program
Finally, here is the code of a program testing the proper functioning of our manager objects. It is noteworthy that I have not discussed in this article the need to ensure that the manager is not destroyed as long as there are instances of objects he manages. Indeed, if so, these bodies attempt at destruction of their access to a manager who no longer exists, with adverse consequences that entails.
# include "ResourceManager.h"
# include "Resource.h"
# include <cassert>
# include <iostream>

int main ()
(
     / / Maximum allowed 3 forums
     ResourceManager mgr (3);
    
     ResourcePtr r1 = mgr.New ();
     ResourcePtr r2 = mgr.New ();
     ResourcePtr r3 = mgr.New ();
     assert (r1 & r2 & & & r3);
     assert (mgr.GetNbInstances () == 3);
    
     / / New () should fail
     ResourcePtr r4 = mgr.New ();
     assert (! r4);
     assert (mgr.GetNbInstances () == 3);
    
     / / Release a forum
     R2 = R1;
     assert (mgr.GetNbInstances () == 2);
    
     / / Now, New () should succeed
     (
         ResourcePtr r_local = mgr.New ();
         assert (mgr.GetNbInstances () == 3);
         assert (r_local);
     )
     assert (mgr.GetNbInstances () == 2);
    
     / / Exception safety test
     Resource: ThrowExceptionDuringNextConstruction ();
     try
     (
         mgr.New ();
     )
     catch (...)
     (
     )
     assert (mgr.GetNbInstances () == 2);
    
     std:: court << "Test ok \ n";
)

Once compiled and executed, it displays:
+ Construction Resource ()
+ Construction Resource ()
+ Construction Resource ()
-- Destruction of Resource ()
+ Construction Resource ()
-- Destruction of Resource ()
x Construction Resource (): exception!
Test OK!
-- Destruction of Resource ()
-- Destruction of Resource ()
 
< Prev   Next >
School Joomla Templates and Joomla Tutorials