RAII è di gran lunga l'idioma più utile in c ++. Tuttavia sembrano esserci due trappole comuni ad esso associate che ritengo debbano essere formalmente affrontate.
Mancato rilascio di una risorsa nel distruttore e invalidazione delle risorse prima della decostruzione. Quali sono le tue opinioni su estensioni di pattern solidi per incorporare queste trappole?
Esempi di errore di rilascio della risorsa:
- Impossibile chiudere un file
- Impossibile arrestare una connessione socket
Esempi di invalidazione delle risorse (riferimento risorsa ciondolante):
- Il socket peer ha chiuso la connessione
- La risorsa è stata revocata dal fornitore di terze parti
Ecco un nuovo oggetto RAII che ho progettato per gestire il problema di invalidazione delle risorse utilizzando il feedback finora (feedback sempre ben accetto):
template<typename T> class shared_weak_ptr;
template<typename T>
class revoke_ptr {
friend class shared_weak_ptr<T>;
public:
typedef std::function<void()> revoke_func;
revoke_ptr(T* t) : _self(std::make_shared<model_t>(t)) {}
void revoke() {
if (!_self->_resource) throw 1; // Already revoked
for (auto func : _self->_revoke_funcs) func();
_self->_revoke_funcs.clear();
_self->_resource.reset();
}
private:
struct model_t {
model_t(T* t) : _resource(t) {}
std::shared_ptr<T> _resource;
std::list<revoke_func> _revoke_funcs;
};
std::shared_ptr<model_t> _self;
};
template<typename T>
class shared_weak_ptr {
public:
template<typename R>
shared_weak_ptr(revoke_ptr<T> revoke_ptr, R revoke_callback) : _self(std::make_shared<const model_t>(std::move(revoke_ptr), std::move(revoke_callback))) {}
std::shared_ptr<T> lock() const { return _self->_revoke_ptr._self->_resource; }
private:
using revoke_func = typename revoke_ptr<T>::revoke_func;
using revoke_it = typename std::list<revoke_func>::iterator;
struct model_t {
model_t(revoke_ptr<T> revoke_ptr, revoke_func revoke_callback) : _revoke_ptr(std::move(revoke_ptr)) {
if (!_revoke_ptr._self->_resource) throw 1; // The resource has already been revoked
_revoke_ptr._self->_revoke_funcs.emplace_back(std::move(revoke_callback));
_revoke_it = _revoke_ptr._self->_revoke_funcs.end();
--_revoke_it;
}
~model_t() {
if (!_revoke_ptr._self->_resource) return; // Already revoked so our callback has already been removed
_revoke_ptr._self->_revoke_funcs.erase(_revoke_it); // Remove our callback
}
revoke_ptr<T> _revoke_ptr;
revoke_it _revoke_it;
};
std::shared_ptr<const model_t> _self;
};
Esempio di utilizzo:
int main() {
revoke_ptr<int> my_revoke_ptr(new int(5));
typedef std::shared_ptr<const uint8_t> unique_key;
unique_key key = std::make_shared<const uint8_t>();
std::map<unique_key, shared_weak_ptr<int>> my_collection;
shared_weak_ptr<int> my_shared_weak_ptr(my_revoke_ptr, [key, &my_collection](){
std::cout << "revoked" << std::endl;
auto it = my_collection.find(key);
if (it != my_collection.end()) {
my_collection.erase(it);
}
});
my_collection.emplace(key, my_shared_weak_ptr);
if (auto ptr = my_shared_weak_ptr.lock()) {
assert(true);
std::cout << *ptr << std::endl;
} else {
assert(false);
std::cout << "nope" << std::endl;
}
my_revoke_ptr.revoke();
if (auto ptr = my_shared_weak_ptr.lock()) {
assert(false);
std::cout << *ptr << std::endl;
} else {
assert(true);
std::cout << "nope" << std::endl;
}
assert(my_collection.find(key) == my_collection.end());
return 0;
}