RAII is by far the most useful idiom in c++. However there seem to be two common pitfalls associated with it that I believe need to be formally addressed.
Failure to release a resource in the destructor and resource invalidation prior to deconstruction. What are your thoughts on solid pattern extensions to incorporate these pitfalls?
Examples of resource release failure:
- Failing to close a file
- Failing to shutdown a socket connection
Examples of resource invalidation (dangling resource reference):
- The socket peer closed the connection
- The resource has been revoked by the third party provider
Here is a rough new RAII object that I designed to handle the resource invalidation problem utilizing the feedback so far (feedback always welcome):
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;
};
Example Usage:
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;
}