VIEW THE CODE BELOW IN FULL-SCREEN (weak_ptr_handle_sample.cpp)
Code: Select all
/*
(c) 2013 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=1066
*/
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <iostream>
#include <cassert>
size_t g_num_allocs = 0;
/// Overloaded global new, just to see allocations.
void * operator new (size_t size)
{
void *ptr = ::malloc(size);
std::cout << "+ new: 0x" << ptr << ", " << size << " byte(s)\n";
++g_num_allocs;
return ptr;
}
/// Overloaded global delete, just to see deallocations.
void operator delete (void *ptr)
{
std::cout << "- delete: 0x" << ptr << "\n";
::free(ptr);
}
/// A Resource that can by instantiated only through the Manager.
class Resource
{
public:
const std::string & name() const { return _name; }
private:
friend class Manager; ///< Resource factory.
Resource();
Resource(const std::string &name) : _name(name) {}
std::string _name;
};
typedef std::shared_ptr<Resource> Resource_holder;
typedef std::weak_ptr<Resource> Resource_handle;
/// A helper that asserts on an attempt to lock an expired weak_ptr.
template <typename T>
std::shared_ptr<T> safe_lock(std::weak_ptr<T> w)
{
std::shared_ptr<T> p(w.lock());
assert(p && "An expired weak_ptr detected!");
return p;
}
/// A Manager that explicitly controls the lifetime of Resources.
class Manager
{
public:
Manager() {}
/// Returns a handle (weak pointer) to the newly created Resource.
Resource_handle create_resource(const std::string &name)
{
_resources.emplace_back(Resource_holder(new Resource(name)));
return _resources.back();
}
/// Destroys an existing Resource using the provided handle (weak pointer).
void destroy_resource(Resource_handle r)
{
assert(std::find(begin(_resources), end(_resources), safe_lock(r)) != end(_resources));
_resources.erase(remove(begin(_resources), end(_resources), safe_lock(r)), end(_resources));
}
private:
Manager(const Manager &); // no copy
Manager & operator = (const Manager &); // no assignment
std::vector<Resource_holder> _resources;
};
int main()
{
//Resource r("A"); // doesn't compile!
//Resource *r = new Resource("A"); // doesn't compile!
Manager mgr;
std::cout << "<X>\n";
Resource_handle r1 = mgr.create_resource("Resource A");
std::cout << safe_lock(r1)->name() << "\n";
std::cout << "<Y>\n";
Resource_handle r2 = r1;
std::cout << safe_lock(r2)->name() << "\n";
std::cout << "<Y>\n";
mgr.destroy_resource(r1);
//m.destroy_resource(r2); // asserts! (double destroy)
//std::cout << safe_lock(r1)->name() << "\n"; // asserts! (already destroyed)
std::cout << "<X>\n";
std::cout << "Total number of allocations: " << g_num_allocs << "\n";
return 0;
}
// Compiled under Visual C++ 2012 (debug), output:
// + new: 0x00816470, 8 byte(s)
// <X>
// + new: 0x0081C3E8, 8 byte(s)
// + new: 0x0081C888, 28 byte(s)
// + new: 0x0081C500, 8 byte(s)
// + new: 0x0081B380, 16 byte(s)
// + new: 0x0081C378, 8 byte(s)
// - delete: 0x0081C3E8
// Resource A
// <Y>
// Resource A
// <Y>
// - delete: 0x0081C500
// - delete: 0x0081C888
// <X>
// Total number of allocations: 6
// - delete: 0x0081B380
// - delete: 0x0081C378
// - delete: 0x00816470
Besides some obvious pros, the approach also has a bunch of cons. There's some overhead caused by additional memory allocations, and more importantly the shared ref. counter that the smart pointers use internally needs to be protected in a multi-threaded environment (each time a std::weak_ptr gets copied or converted to std::shared_ptr, then when the std::shared_ptr goes out of the scope), which might prove quite expensive. Please note that typical handles don't suffer from this. Handles are also semantically clearer as they serve as pure identifiers that encourage one to think twice before invoking an operation through them.