0

Let's assume I have a class which manages some resource in RAII way:

class C
{
   HANDLE hResource_;

   // prevent sharing the ownership over the resource among multiple instances of C
   C(const C&);
   C& operator=(const C&);

public:
   C() : hResource_(INVALID_HANDLE){}

   C(int arg1, const std::string& arg2,...)
   {
      ...
      allocResource(arg1, arg2, ...);
      ...
   }

   ~C
   {
      ...
      FreeResource(hResource_);
      hResource_ = INVALID_HANDLE;
      ...
   }

   void allocResource(int arg1, const std::string& arg2, ...)
   {
      if(hResource_ == INVALID_HANDLE)
      {
          hResource_ = AllocateResource(arg1, arg2,...);
      }
   }

   HANDLE handle() {return hResource_;}
};

Its constructor takes some parameters needed for resource allocation and I am able to create an instance of it, use it and let it live within some scope:

// some global function 
void goo()
{
   C c(123, "test");
   UseResource(c.handle(),...);
   ... 
}

Let's say I want now an instance of C to be a member of some class, and want to delay allocation of the resource that happens in C's c-tor. This requires C's default c-tor and some C's member function which performs resource allocation (e.g. allocResource() which calls AllocateResource()).

class A
{
   C c_;

public:
   void foo1()
   {
      ...
      c_.allocResource(123, "test"); 
      UseResource(c_.handle(),...);
      ...
   }   

   void foo2()
   {
      ...         
      UseResource(c_.handle(),...);
      ...
   }   
};

By using dedicated function, we are exposing C's internals in some way which I don't like.

My question is: Is this approach a common way to enable lazy initialization? Are there any alternatives?


EDIT: This is a possible class design regarding (MSalters') suggestions below:

class C
{
   HANDLE hResource_;

   // prevent sharing the ownership over the resource 
   // among multiple instances of C
   C(const C&);
   C& operator=(const C&);

public:      

   // prevent object creation if resource cannot be acquired
   C(int arg1, const std::string& arg2,...)
   {          
      hResource_ = AllocateResource(arg1, arg2,...);

      // assumption: AllocateResource() returns 
      // INVALID_HANDLE in case of failure
      if(hResource_ == INVALID_HANDLE)
         throw resource_acquisition_exception();
   }

   ~C
   {
      ...
      FreeResource(hResource_);
      hResource_ = INVALID_HANDLE;
      ...
   }

   HANDLE handle() {return hResource_;}
};

class A
{
   std::unique_ptr<C> c_;

public:
   void foo1()
   {
      try
      {
         ...
         c_ = std::unique_ptr<C>(new C(123, "test"));
         UseResource(c_->handle(),...);
         ...
      }
      catch(const resource_acquisition_exception& exc)
      {
         ...
      }
      catch(...)
      {
         ...
      }
   }   

   void foo2()
   {
      ...         
      UseResource(c_->handle(),...);
      ...
   }   
};
Bojan Komazec
  • 9,216
  • 2
  • 41
  • 51
  • What about implement a move constructor? So that you can just create a new C object inside foo1 and assign it to c_. In c++03 you can do that by using a constructor which takes a C non-const reference. – sbabbi Apr 17 '12 at 11:48
  • You ought to move your possible solution into an answer rather than putting it into the question. – Keith Pinson Nov 27 '12 at 20:06

2 Answers2

4

No, this is not a common way to do RAII. In fact, it's not RAII at all. If you can't allocate the necessary resources for a C, don't create a C.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I can see now that RAII and lazy initialization cannot go hand in hand. But what still confuses me is the fact that we cannot have `C` as a member of `A` but rather having `HANDLE` as its member instead. – Bojan Komazec Apr 17 '12 at 11:38
  • They do go hand in hand if you get the structure right. In your class `A`, add a `std::unique_ptr c_`. Class `C` can then be a RAII wrapper for `HANDLE`. You now have a lazy initialization of `C`, not just `HANDLE`. BTW, don't forget the copy ctor and assignment of `C`. – MSalters Apr 17 '12 at 11:43
  • @MSalters: actually that also relies (more or less) on lazily initializing `std::unique_ptr` – KillianDS Apr 17 '12 at 11:52
  • @MSalters `C` is indeed intended to be a RAII wrapper for `HANDLE`. So basically it is impossible to have lazy initialization if `C` instance itself is a member of `A`? Is the concept you suggested (with `unique_ptr`) a common approach to this problem? – Bojan Komazec Apr 17 '12 at 11:54
  • @KillianDS: Indeed. But that's a good example of separation of concerns. `class C` knows about the details of `HANDLE`. `std::unique_ptr` indirectly knows about handles, because it knows to call `C::~C`, yet the author of `std::unique_ptr` did not have to put this information in. That's one benefit of RAII classes: they play well with the standard classes. – MSalters Apr 17 '12 at 11:59
  • @BojanKomazec: The problem with your solution is that C has two responsibilities: implement RAII behavior (map object behavior to a C API for handles) and implement lazy initialization. Since `unique_ptr` is an existing solution (as is `boost::optional`) for the latter responsibility, it's easy to separate the two concerns. – MSalters Apr 17 '12 at 12:05
  • @MSalters Brilliant observation! In the EDIT section of my question I've just added a possible class implementation regarding your advice. That solves my problem. The story behind this is that in the real project I have many `C`-like classes and a their single container (`A`). At the beginning `A` was dealing with many raw handles and their `Allocate` and `Free` functions so I decided to create (`C`-like) RAII wrappers for each of them. – Bojan Komazec Apr 17 '12 at 13:08
  • @Bojan Komazec: Yup, looks very familiar now. – MSalters Apr 19 '12 at 07:53
  • @MSalters Thank you for your help. I learned a lot from this example. – Bojan Komazec Apr 19 '12 at 08:49
1

The issue is indeed that you're exposing the internals of C, but you are already doing that with the handle() function, which already limits the chance of doing lazy instantiation.

It would be easier if C was actually called to do something instead of just getting the handler. However, since handle() is a getter and you can already pass the needed parameters in the constructor (without instantiating, but by storing the parameters), You can check in handle() whether hResource_ is valid and if not, allocate the resource (and throw an exception if allocating fails).

stefaanv
  • 14,072
  • 2
  • 31
  • 53