#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
бы принимали ashared_ptr<>
, а не необработанный указатель.4. Если в классе
User
у меня есть член типаResource
, но я создаю его в списке инициализаторовUser
конструктора, не будет лиResource
объект создан доUser
? Разве это не означает, чтоResource
участник никогда не будет находиться в «состоянии зомби»? Или это не то, что вы имели в виду?5. @dandrestor: AFAIK,
Resource
будет создан в порядке списка инициализаторов после выделенияsizeof(User)
памяти. Состояние зомби — это состояние, в котором объект больше не полезен, но и разыменование объекта не вызовет ошибки.