Dan Nestor
Dan Nestor

Reputation: 2531

Passing resource-locking object as parameter

What is a good way of passing an object as a parameter? In my code, I have been using references instead of pointers, and I would like to stick to this approach if possible. However, there is one case where this does not work properly:

class Resource {
    Resource(...) { //expensive resource initialization }
    ~Resource() { //destroy resource }
};

class User {
    User(const Resource & res) : res_(res) { //initialize user }
private:
    Resource res_;
}

void Init() {
    Resource res(...);
    User user = new User(res);
} //res is destroyed - along with its associated resource
//user can no longer use the resource

Are there any best practices that should be used in such a scenario? The result I want to achieve is to destroy the Resource automatically when the User object is destroyed, but not rely on the User object to create the Resource itself. My feeling is that I should overload the copy constructor of Resource. Any other ideas?

Upvotes: 0

Views: 166

Answers (1)

ali_bahoo
ali_bahoo

Reputation: 4863

Your best shot is holding a shared_ptr<Resource> member in User class. But this requires the resource to be generated on the free (heap) memory.

class User {
  User(Resource * res) : res_ptr(res) { //initialize user }
private:
  shared_ptr<Resource> res_ptr;
}

void Init() {
  Resource * res = new Resource(...);
  User user = new User(res);
} 

Second approach is providing a fake copy constructor and a swap mechanism for Resource.

class Resource
{
private:
  explicit Resource(const Resource &); // disabled to avoid heavy copy.
  const Resource & operator = (const Resource & );
  int * int_res;
public:
  Resource() : int_res(new int(100)) { } 

  ~Resource()
  {
    if(int_res != NULL)
      delete int_res;
  }

  Resource(Resource & other) : int_res(NULL) 
  {
    this->swap(other);
  }

  void swap(Resource & other)
  {
    using std::swap;
    swap(int_res, other.int_res);
  }
};

class User
{
private:
  Resource resource;
  User();
  User(const User &);
  const User & operator = (const User &);
public:
  User(Resource & res) : resource(res) {  }
  ~User() { }
};

But this is dangerous and error-prone because after creating the user, you leave the resource in a zombie state. For this piece of code, all access to the int_res pointer, which is assgined to NULL, will give you access violation error.

The last approach I can explain is using a C++0x feature "move semantics".

class Resource
{
// ...
// ...
  // Replace with Resource(Resource & other)
  Resource(Resource && other) : int_res(NULL) //Move constructor
  {
    this->swap(other);
  }

  const Resource & operator = (Resource && other) // Move assignment operator
  {
      this->swap(other);
      return *this;
  }

  void swap(Resource & other)
  {
    using std::swap;
    swap(int_res, other.int_res);
  }
};

class User
{
private:
  Resource resource;
  User();
  User(const User &);
  const User & operator = (const User &);
public:
  User(Resource && res) : resource(std::move(res)) {  }
  ~User() { }
};

void Init() {
    Resource res(...);
    User user = new User(std::move(res));
}

This approach is some how safer. You don't have to deal with pointers and if you forget to write std::move, you will get a compiler error saying "you cannot bind a Resource instance to Resource&&" or "copy construtor of Resource is disabled."

&& emphasizes the object is temporary and is going to evaporate. So this approach is more suitable for scenarios where Resource is generated and returned by a function. If I were you I would make the constructor private and generate it with a friend function.

Resource GetResource(....)
{
  Resource res(...);
  return res;
}

void Init() 
{     
  User user = new User( GetResource(...) );
}

This piece of code is perfectly performant with "move semantics". You must learn it if you are able to write C++0x code. This and this are two good videos to start with.

Upvotes: 1

Related Questions