Передача объекта блокировки ресурсов в качестве параметра - PullRequest
0 голосов
/ 30 октября 2011

Что такое хороший способ передачи объекта в качестве параметра?В моем коде я использовал ссылки вместо указателей, и я хотел бы придерживаться этого подхода, если это возможно.Однако есть один случай, когда это не работает должным образом:

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

Есть ли какие-либо передовые практики, которые следует использовать в таком сценарии?В результате я хочу автоматически уничтожить Resource при уничтожении объекта User, но не полагаться на объект User для создания самого Resource.У меня такое чувство, что я должен перегрузить конструктор копирования Resource.Есть другие идеи?

1 Ответ

1 голос
/ 01 ноября 2011

Ваш лучший выстрел - член shared_ptr<Resource> в классе User. Но для этого требуется, чтобы ресурс генерировался в свободной (куче) памяти.

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);
} 

Второй подход - предоставить конструктор поддельных копий и механизм подкачки для 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() { }
};

Но это опасно и подвержено ошибкам, потому что после создания пользователя вы оставляете ресурс в состоянии зомби. Для этого фрагмента кода весь доступ к указателю int_res, который назначен на NULL, приведет к ошибке нарушения доступа.

Последний подход, который я могу объяснить, - это использование в C ++ 0x функции "семантика перемещения".

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));
}

Этот подход несколько безопаснее. Вам не нужно иметь дело с указателями, и если вы забудете написать std::move, вы получите сообщение об ошибке компилятора, говорящее «вы не можете привязать экземпляр Resource к Resource&&» или «отключено создание копии Resource «.

&& подчеркивает, что объект является временным и собирается испаряться. Таким образом, этот подход больше подходит для сценариев, где Resource генерируется и возвращается функцией. На вашем месте я бы сделал конструктор закрытым и сгенерировал бы его с помощью функции друга.

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

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

Этот фрагмент кода идеально соответствует "семантике перемещения". Вы должны изучить это, если вы можете написать код C ++ 0x. Это и , это - это два хороших видео для начала.

...