Передача объекта блокировки ресурсов в качестве параметра

#c #reference #raii

#c #ссылка #раии

Вопрос:

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

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

class User {
    User(const Resource amp; 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. Вы хотите разделить Resource объект между User объектами или просто создать их внешними по отношению к User объектам?

2. Мне также нужно разделить их между User объектами.

3. Не зная больше о дизайне, я предполагаю, что вы хотите что-то вроде shared_ptr , которое доступно в boost, tr1 или стандарте c 11, в зависимости от возраста вашей цепочки инструментов. boost.org/doc/libs/1_47_0/libs/smart_ptr/shared_ptr.htm

Ответ №1:

Ваш лучший шанс — удержать 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 amp;); // disabled to avoid heavy copy.
  const Resource amp; operator = (const Resource amp; );
  int * int_res;
public:
  Resource() : int_res(new int(100)) { } 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Комментарии:

1. User(Resource * res) : res_ptr(res) Не смешивайте и не сочетайте — shared_ptr<> используйте указатели use или raw, а не комбинацию того и другого.

2. @ildjarn: я создаю shared_ptr внутренний User класс с указателем на Resource . В чем заключается обратная сторона этого?

3. Недостатком является то, что Resource он становится владельцем необработанного указателя — эта семантика неочевидна. Однако они были бы, если Resource бы принимали a shared_ptr<> , а не необработанный указатель.

4. Если в классе User у меня есть член типа Resource , но я создаю его в списке инициализаторов User конструктора, не будет ли Resource объект создан до User ? Разве это не означает, что Resource участник никогда не будет находиться в «состоянии зомби»? Или это не то, что вы имели в виду?

5. @dandrestor: AFAIK, Resource будет создан в порядке списка инициализаторов после выделения sizeof(User) памяти. Состояние зомби — это состояние, в котором объект больше не полезен, но и разыменование объекта не вызовет ошибки.