Как следует обрабатывать ошибку во время освобождения ресурса, когда
объект, представляющий ресурс, содержится в общем указателе?
РЕДАКТИРОВАТЬ 1:
Чтобы сформулировать этот вопрос более конкретно: многие интерфейсы в стиле C
есть функция для выделения ресурса и одна для освобождения
Это. Примеры open (2) и close (2) для файловых дескрипторов в POSIX
системы, XOpenDisplay и XCloseDisplay для подключения к X
сервер, или sqlite3_open и sqlite3_close для подключения к
База данных SQLite.
Мне нравится инкапсулировать такие интерфейсы в класс C ++, используя Pimpl
идиома, чтобы скрыть детали реализации и предоставление фабрики
метод, возвращающий общий указатель, чтобы убедиться, что ресурс
освобождается, когда на него не осталось никаких ссылок.
Но во всех приведенных выше и многих других примерах функция
используется для освобождения ресурса может сообщить об ошибке. Если эта функция
вызванный деструктором, я не могу выбросить исключение, потому что
как правило, деструкторы не должны бросать.
Если, с другой стороны, я предоставляю публичный метод для
ресурс, у меня теперь есть класс с двумя возможными состояниями: одно, в котором
ресурс действителен, и тот, в котором ресурс уже был
вышел. Это не только усложняет реализацию
класс, это также открывает потенциал для неправильного использования. Это плохо, потому что
интерфейс должен быть нацелен на то, чтобы ошибки при использовании были невозможны.
Буду благодарен за любую помощь в решении этой проблемы.
Первоначальная постановка вопроса и мысли о возможном
Решение следуйте ниже.
РЕДАКТИРОВАТЬ 2:
Теперь есть щедрость на этот вопрос. Решение должно соответствовать этим
Требования:
- Ресурс освобождается тогда и только тогда, когда на него не осталось ссылок.
- Ссылки на ресурс могут быть явно уничтожены. Исключение выдается, если при освобождении ресурса произошла ошибка.
- Невозможно использовать ресурс, который уже был освобожден.
- Подсчет ссылок и освобождение ресурса поточно-ориентированный .
Решение должно соответствовать этим требованиям:
- Используется общий указатель, предоставленный boost , C ++ Технический отчет 1 (TR1) и следующий стандарт C ++, C ++ 0x .
- Это общее. Классы ресурсов должны только реализовывать способ освобождения ресурса.
Спасибо за ваше время и мысли.
РЕДАКТИРОВАТЬ 3:
Спасибо всем, кто ответил на мой вопрос.
ответ Алска встретил все, что просили в награде, и
был принят В многопоточном коде это решение потребует
отдельная тема очистки.
Я добавил еще один ответ , где любые исключения во время
очистки выбрасываются потоком, который фактически использовал ресурс,
без необходимости отдельной очистки потока. Если вы все еще
интересуюсь этой проблемой (меня это сильно беспокоит), пожалуйста
комментарии.
Умные указатели являются полезным инструментом для безопасного управления ресурсами. Примеры
такими ресурсами являются память, дисковые файлы, соединения с базой данных или
сетевые подключения.
// open a connection to the local HTTP port
boost::shared_ptr<Socket> socket = Socket::connect("localhost:80");
В типичном сценарии класс, инкапсулирующий ресурс, должен быть
не копируемый и полиморфный. Хороший способ поддержать это - предоставить
фабричный метод, возвращающий общий указатель и объявляющий все
конструкторы непубличные. Общие указатели теперь можно скопировать из
и назначен свободно. Объект автоматически уничтожается, когда нет
ссылка на него остается, и деструктор затем освобождает
ресурс.
/** A TCP/IP connection. */
class Socket
{
public:
static boost::shared_ptr<Socket> connect(const std::string& address);
virtual ~Socket();
protected:
Socket(const std::string& address);
private:
// not implemented
Socket(const Socket&);
Socket& operator=(const Socket&);
};
Но есть проблема с этим подходом. Деструктор не долженthrow, поэтому сбой при освобождении ресурса останется незамеченным.
Распространенным выходом из этой проблемы является добавление публичного метода в релиз
ресурс.
class Socket
{
public:
virtual void close(); // may throw
// ...
};
К сожалению, при таком подходе возникает другая проблема: наши объекты
может теперь содержать ресурсы, которые уже были освобождены. это
усложняет реализацию класса ресурсов. Еще хуже, это
позволяет клиентам класса использовать его неправильно.
Следующий пример может показаться надуманным, но это распространенная ошибка в
многопоточный код.
socket->close();
// ...
size_t nread = socket->read(&buffer[0], buffer.size()); // wrong use!
Либо мы гарантируем, что ресурс не будет освобожден до объекта
уничтожается, теряя тем самым любой способ справиться с неисправным ресурсом
открепление. Или мы предоставляем способ освободить ресурс явно
в течение срока службы объекта, что позволяет использовать
неверный класс ресурсов.
Есть выход из этой дилеммы. Но решение предполагает использование
модифицированный класс общего указателя. Эти модификации, вероятно, будут
спорно.
Типичные реализации разделяемых указателей, такие как boost :: shared_ptr,
требуют, чтобы исключение не создавалось, когда деструктор их объекта
называется. Как правило, ни один деструктор не должен бросать, так что это
разумное требование Эти реализации также позволяют настраивать
указывается функция удаления, которая вызывается вместо
деструктор, когда нет ссылки на объект. Без броска
требование распространяется на эту пользовательскую функцию удаления.
Смысл этого требования ясен: общий указатель
деструктор не должен бросать. Если функция удаления не выбрасывает, ни
будет деструктор общего указателя. Однако то же самое относится и к
другие функции-члены общего указателя, которые ведут к ресурсу
освобождение, например reset (): в случае сбоя освобождения ресурса нет
может быть выдано исключение.
Предлагаемое здесь решение заключается в том, чтобы пользовательские функции удаления могли
бросить. Это означает, что деструктор модифицированного общего указателя должен
ловить исключения, выданные функцией удаления. С другой стороны,
функции-члены, отличные от деструктора, например, сброс (), не должен
перехват исключений функции удаления (и их реализация
становится несколько сложнее).
Вот оригинальный пример использования функции удаления метания:
/** A TCP/IP connection. */
class Socket
{
public:
static SharedPtr<Socket> connect(const std::string& address);
protected:
Socket(const std::string& address);
virtual Socket() { }
private:
struct Deleter;
// not implemented
Socket(const Socket&);
Socket& operator=(const Socket&);
};
struct Socket::Deleter
{
void operator()(Socket* socket)
{
// Close the connection. If an error occurs, delete the socket
// and throw an exception.
delete socket;
}
};
SharedPtr<Socket> Socket::connect(const std::string& address)
{
return SharedPtr<Socket>(new Socket(address), Deleter());
}
Теперь мы можем использовать reset () для явного освобождения ресурса. Если там есть
все еще ссылка на ресурс в другом потоке или другой части
программа, вызывающая reset (), будет только уменьшать ссылку
сосчитать. Если это последняя ссылка на ресурс, ресурс
вышел. В случае сбоя освобождения ресурса выдается исключение.
SharedPtr<Socket> socket = Socket::connect("localhost:80");
// ...
socket.reset();
EDIT:
Вот полная (но зависящая от платформы) реализация средства удаления:
struct Socket::Deleter
{
void operator()(Socket* socket)
{
if (close(socket->m_impl.fd) < 0)
{
int error = errno;
delete socket;
throw Exception::fromErrno(error);
}
delete socket;
}
};