
You can do this with shared_ptr/weak_ptr.
You are right. I discovered that some time ago, but found this solution more cumbersome to use compared to the one I already had (live_ptr). With shared_ptr/weak_ptr you have to: - add a data member of type shared_ptr - remember to initialize it in all constructors - add way to obtain weak_ptrs from the stored shared_ptr - probably by adding an extra member function Last but not least, despite of all the optimization effort people put into these classes, shared_ptr/weak_ptr combination is quite slow for that task. I have run several tests with full optimization enabled in Visual Studio C++ 2003, P4 2.4GHz: [live_ptr | shared_ptr/weak_ptr] Single threaded: 0.921s | 1.891s (live_ptr is 2x faster) Multi threaded: 1.015s | 8.063s (live_ptr is 8x faster) Below is source code for that test: #include <iostream> #include <ctime> #include <vector> #include <boost/weak_ptr.hpp> #include <boost/shared_ptr.hpp> #include "live_ptr.hpp" using namespace std; using namespace boost; // Class for testing live_ptr performance class A: public live_ptr_target<A> { public: live_ptr<A> r1, r2, r3, r4; }; // Class for testing shared_ptr/weak_ptr performance class B { shared_ptr<B> sp; public: B() { sp.reset(this); } weak_ptr<B> get_ref() { return weak_ptr<B>(sp); } void destroy() { sp.reset(); } weak_ptr<B> r1, r2, r3, r4; }; void testA() { vector<A *> v; for (int i = 0; i < 1000; ++i) // Create objects v.push_back(new A); for (int i = 0; i < 1000; ++i) // Initialize live references { A *a = v[i]; a->r1 = v[i % 8]; a->r2 = v[i % 32]; a->r3 = v[i % 128]; a->r4 = v[i % 512]; } for (int i = 0; i < 1000; ++i) // Juggle live references { A *a = v[i]; a->r1 = a->r2; a->r2 = a->r3; a->r3 = a->r4; a->r4 = a->r1; } for (int i = 0; i < 1000; ++i) // Delete the objects delete v[i]; } void testB() { vector<B *> v; for (int i = 0; i < 1000; ++i) // Create objects v.push_back(new B); for (int i = 0; i < 1000; ++i) // Initialize live references { B *b = v[i]; b->r1 = v[i % 8]->get_ref(); b->r2 = v[i % 32]->get_ref(); b->r3 = v[i % 128]->get_ref(); b->r4 = v[i % 512]->get_ref(); } for (int i = 0; i < 1000; ++i) // Juggle live references { B *b = v[i]; b->r1 = b->r2; b->r2 = b->r3; b->r3 = b->r4; b->r4 = b->r1; } for (int i = 0; i < 1000; ++i) // Delete the objects v[i]->destroy(); } int main() { clock_t t1 = clock(); for (int i = 0; i < 1000; ++i) test1(); clock_t t2 = clock(); cout << "T1: " << double(t2 - t1) / CLOCKS_PER_SEC << endl; t1 = clock(); for (int i = 0; i < 1000; ++i) test2(); t2 = clock(); cout << "T2: " << double(t2 - t1) / CLOCKS_PER_SEC << endl; return 0; }
The problem with providing such an interface is lack of reentrancy and thread safety. Consider this code:
That is a valid remark, I didn't think about it before. My applications never used live_ptrs across threads. I see several solutions to that problem: 1. provide locking interface (as in weak_ptr) instead of direct access 2. leave is at it is, and let user deal with synchronization, if any is needed (not every class must be thread safe) 3. provide two separate classes, one implementing 1. and the other 2.
Your live_ptr<> requires support from Tank, right?
Yes, current implementation requires a class to inherit from live_ptr_target<T>. But I understand that being 2x faster and a little more straightforward to use might not be enough to mandate addition to boost. cheers, Marcin Kalicinski