Manage resources by using smart pointer RAII objects

A common way to create objects is by using a factory method or an abstract factory. Such a factory will normally create and return the object and handover the responsibility over the object to the client. Therefore the client has to release the created object to free no longer needed resources. The following source code shows a typical implementation pattern. The object is created by using a factory and released at the end of a function call.

class MyObject
{
};

MyObject* CreateMyObject()
{
  return new MyObject();
}

int _tmain(int argc, _TCHAR* argv[])
{
  MyObject *pMyObject = CreateMyObject();

  // ...

  delete pMyObject;
  
	return 0;
}

 

This looks fine in the first moment but this pattern is a source for errors. It may happen that the „delete“ statement is never executed. For example an error may occur in one of the statements before „delete“ and the function returns early. But not only runtime errors may occur, this pattern will also increase the possibility of implementation errors, especially in huge functions were the factory and the delete statements are far away from each other. For example a developer may add parameter checks or checks the return values of sub-function calls and return in case of wrong parameters or results.

To avoid such issues you should manage resources in own objects. If the object scope is left, the object will be destroyed and the destructor is called. This is done in any case, independent whether the function is executed completely, an early return is called or an error occurred.

By using a resource management object you can free resources in the dtor and you don’t have to think about all the possible ways the object scope is left.

This concept is often called RAII (resource acquisition is initialization). It means that objects acquire resources in their constructors and release them in their destructors.

Fur such simple cases like the one above you don’t have to implement an own object. Instead you can use an existing one offered by the STL, for example a shared pointer.

int _tmain(int argc, _TCHAR* argv[])
{
  std::shared_ptr<MyObject> pMyObject(CreateMyObject());

  // ...

  return 0;
}

 

Within the destructor, the shared pointer will delete the containing object. So it’s destructor is invoked and you have the possibility to release resources. You have to have one importand thing in mind whenever you use a shared pointer: it calls “delete” but not “delete[]”. Therefore you cannot use it for dynamically allocated arrays. That’s because vector and string can almost always replace dynamically allocated arrays and should be used instead. In case you need a shared pointer for arrays you can find one within the Boost library (boost::shared_array).

Another important guideline is to create the shared pointer within an own statement and do not use it inside another statement. Let us use the previous example to explain why. For example you want to execute a function „DoSomething“ which has two parameters, the object pointer and an object with application settings. These settings are read by a function “GetApplicationSetting()”. So you may call the DoSomething function and execute the creation of the shared pointer and the function call for the application settings as nested statements.

int _tmain(int argc, _TCHAR* argv[])
{
  DoSomething(std::shared_ptr<MyObject> pMyObject(CreateMyObject()), GetApplicationSettings());

  return 0;
}

 

Beside the fact that such nested function calls are difficult to read, this call may leak resources. But why? We use an object to manage the resources and as we have learned so far this should solve the resource leaking issue.

We have to think about the possibilities of the compiler to understand this behavior. The compiler has to add the following steps.

  • call GetApplicationSetting()
  • execute factory.CreateMyObject)
  • call std::shared_ptr ctor

But the compiler does not execute them in this order. He can change the execution order to create more efficient code. So the compiler can choose the following order:

  • execute factory.CreateMyObject)
  • call GetApplicationSetting()
  • call std::shared_ptr ctor

What will happen if “GetApplicationSetting()” throws an exception? In this case we create our object by calling the factory method but we have not yet stored it within the shared pointer. So we end up in a resource leak as the dtor of the object is never called. To avoid this issue we should create new objects and store them within the smart pointer in a standalone statement. Furthermore to increase source code readability I would recommend avoiding nested function calls in general. So I prefer to call “GetApplicationSettings()” in a standalone statement too.

int _tmain(int argc, _TCHAR* argv[])
{
  std::shared_ptr<MyObject> pMyObject(CreateMyObject());
  ApplicationSettings mySettings = GetApplicationSettings()

  DoSomething(pMyObject, mySettings);

  return 0;
}

 

Summary

Follow the RAII concept and use objects to manage resources. Implement the resource creation and the management object creation in a standalone statement.

Werbung
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s