Является ли приемлемым стиль C ++, позволяющий копировать классы, которые обертывают общие дескрипторы?
Очень часто я нахожу себя пишущим классы, которые скрывают подробности какой-то мрачной библиотеки C или ресурса ОС, держа за ним shared_ptr за интерфейсом класса. Например.
class window
{
public:
window() : m_handle(OsCreateWindow(), OsDestroyWindow) {}
void make_it_dance();
void paint_it_red();
private:
shared_ptr<OS_WINDOW_HANDLE> m_handle;
}
Потому что классы копируемые и shared_ptr
делает тяжелую работу, экземпляры могут быть переданы волей-неволей, и ничто не протекает или уничтожается дважды. Так что технически все хорошо. Я делал это годами.
Но недавно я начал задаваться вопросом, действительно ли это хороший стиль. В конце концов, объект в конце ручки не дублируется, когда экземпляр класса. Было бы лучше сделать все эти классы не копируемыми, чтобы пользователям было ясно, что они имеют дело со ссылкой на один и тот же объект? Современный C ++ делает большую часть того, чтобы быть «основанным на значениях», и разделение внутреннего ресурса между экземплярами, кажется, идет вразрез с этим принципом.
Однако следствием этого будет то, что большая часть моего кода будет иметь дело с указателями, даже если они являются умными указателями. Это кажется шагом назад.
Я думаю, я понимаю, в чем твоя дилемма. И у меня есть довольно глупое предложение.
Так как ваша проблема не функциональна, а скорее с ожиданием, что копирование вашего window
экземпляр создает, не лучше ли назвать такой класс как этот window_handle
вместо window
?
Это будет означать, что это просто дескриптор некоторого окна, и его копирование не создает нового окна или чего-либо подобного, а просто дублирует дескриптор.
Подчеркну, я предлагаю вам сохранить свой дизайн (который мне кажется удачным и, похоже, работает для вас) и просто изменить свое наименование, чтобы изменить ожидания более высоких уровней кода.
Лично я не позволю копировать и согласен с тем, что использование общего указателя — это шаг назад. Я также добавил бы, что каждый экземпляр «Window» должен содержать уникальный OS_WINDOW_HANDLE.
Вы также можете использовать подход MFC для передачи маркеров между объектами. MFC использует методы Attach и Detach примерно так:
class Window
{
public:
void Attach (OS_WINDOW_HANDLE handle)
{
ASSERT(NULL == m_handle); // or you could destroy the existing handle
m_handle = handle;
}
OS_WINDOW_HANDLE Detach()
{
OS_WINDOW_HANDLE retVal = m_handle;
m_handle = NULL;
return retVal;
}
private:
// disable copy constructor and assignment
Window(const Window&);
Window& operator=(const Window&);
};
Если ресурс, на который указывает дескриптор, делает не нужно дублировать (мелкая копия) использовать std::shared_ptr
, Если ресурс делает нужно дублировать (глубокая копия) вы должны использовать std::unique_ptr
, Если копирование не требуется, используйте std::unique_ptr
,
Возможный ответ (и ситуация, которая вызвала вопрос) состоит в том, что при тихом совместном использовании поддерживающего объекта пользователю класса трудно убедиться, что объект «мертв» в определенной точке.
Например, объект может быть сетевым соединением, и пользователь должен знать из соображений безопасности, что соединение было закрыто.
Если оболочка соединения является копируемой и разделяет соединение между экземплярами, то пользователь должен изучить пути, по которым может проходить экземпляр, чтобы убедиться, что копия не может быть сохранена где-то неожиданно, и держать соединение открытым.
С другой стороны, если обертка не подлежит копированию, существует только один экземпляр, чтобы убедиться, что он мертв. Ссылки все еще могут быть выданы, но как только оригинальная оболочка умрет, пользователь может быть уверен, что объект поддержки тоже мертв.
Если пользователь хочет чтобы иметь общие копии, они всегда могут восстановить это с shared_ptr снова. Но на этот раз решение за ними.