Hi, there
Today I've stepped into a problem I thought was so common I got myself wondering why only now I've noticed it. Imagine you have a class you want to handle using a boost::shared_ptr<>
. This class has members that must forward shared_ptr<>
s of itself to other methods; in other words, it has to create a shared_ptr<>
s to the this
pointer. The way to do that is to hold a weak_ptr
inside the class and use a static member as a factory to new
an instance of itself, wrap it inside a shared_ptr<>
, setup the member weak_ptr<>
and return the managed shared_ptr<>
, per told to do in boost::shared_ptr<>
online documentation (http://www.boost.org/doc/libs/1_40_0/libs/smart_ptr/shared_ptr.htm#Introduction):
Because the implementation uses reference counting, cycles of shared_ptr instances will not be reclaimed. For example, if main() holds a shared_ptr to A, which directly or indirectly holds a shared_ptr back to A, A's use count will be 2. Destruction of the original shared_ptr will leave A dangling with a use count of 1. Use weak_ptr to "break cycles."
The problem lies in the fact that this class can be inherited, and the derived classes have the same requirement of having a weak_ptr<>
to themselves.
So I thought about this pattern I called the "Rabbit Hole" that makes it possible to, in a way, do something equivalent to "overriding a member's type".
It works by having the member you would want to "override" in inherited classes inside template Sonetto::RabbitHole<>
:
class Base
{
protected:
Sonetto::RabbitHole<MemberBaseClass> mMember;
};
Calling mMember.get()
returns a reference of type MemberBaseClass for use. If the Base class is instantiated, mMember.get()
will really return an instance of MemberBaseClass. But let's add another rabbit hole level to the equation, like this:
class Derived : public Base
{
public:
Derived()
{
Base::mMember.link(mMember);
}
protected:
Sonetto::RabbitHoleLevel<MemberBaseClass,MemberDerivedClass> mMember;
};
This time we use template Sonetto::RabbitHoleLevel<>
. The first template parameter is the type required to be returned by the previous rabbit hole level, which, in this case, is MemberBaseClass
. The second template parameter is the type which will be actually instantiated if Derived::mMember
is the deepest rabbit hole level in the object. Deeper level members must, obviously, be inherited from this type. The constructors are responsible for chaining levels like shown in the snippet. When it's time for use the pointer, each class can safely use their own wanted type:
void Base::doSomething()
{
MemberBaseClass &ref = mMember.get();
// [...]
}
void Derived::doSomething()
{
MemberDerivedClass &ref = mMember.get();
// [...]
}
// And so on...
What I wanted to ask about it is for people to help me find what types of inheritance schemes could render the pattern useless. Also, the bad thing about those is that it adds 12 bytes of overhead for each rabbit hole level, so, for a class with two rabbit holes and two inherited classes, there will be a big overhead of 72 bytes. Do you think such an overhead makes this class a bad class to use in many places of my code? Code follows:
// SonettoRabbitHole.h
#ifndef SONETTO_RABBITHOLE_H
#define SONETTO_RABBITHOLE_H
#include <cstdlib>
namespace Sonetto
{
// ---------------------------------------------------------------------------------
// Sonetto::IHalfTypedRabbitHole declaration
// ---------------------------------------------------------------------------------
template <class B>
class IHalfTypedRabbitHole
{
public:
virtual ~IHalfTypedRabbitHole() {}
virtual B &get() = 0;
};
// ---------------------------------------------------------------------------------
// Sonetto::RabbitHoleLevel declaration
// ---------------------------------------------------------------------------------
template <class B,class T>
class RabbitHoleLevel : public IHalfTypedRabbitHole<B>
{
public:
inline RabbitHoleLevel();
virtual ~RabbitHoleLevel();
template <class _T,class D>
void link(RabbitHoleLevel<_T,D> &link)
{
mNextLevel = &link;
}
inline virtual T &get();
private:
IHalfTypedRabbitHole<T> *mNextLevel;
T *mImpl;
};
// ---------------------------------------------------------------------------------
// Sonetto::RabbitHole declaration
// ---------------------------------------------------------------------------------
template <class T>
struct RabbitHole : public RabbitHoleLevel<T,T> {};
// ---------------------------------------------------------------------------------
// Sonetto::RabbitHoleLevel template and inline implementations
// ---------------------------------------------------------------------------------
template <class B,class T>
inline RabbitHoleLevel<B,T>::RabbitHoleLevel()
: mNextLevel(NULL), mImpl(NULL) {}
// ---------------------------------------------------------------------------------
template <class B,class T>
RabbitHoleLevel<B,T>::~RabbitHoleLevel()
{
// If we are at the end of the rabbit hole, we
// delete what we have instantiated here
if (mNextLevel == NULL)
{
delete mImpl;
}
}
// ---------------------------------------------------------------------------------
template <class B,class T>
inline T &RabbitHoleLevel<B,T>::get()
{
if (mImpl == NULL)
{
if (mNextLevel)
{
mImpl = &mNextLevel->get();
}
else
{
mImpl = new T();
}
}
return *mImpl;
}
// ---------------------------------------------------------------------------------
}
#endif
// main.cpp to demonstrate usage
#include <iostream>
#include "SonettoRabbitHole.h"
// "Base 'virtual' member" declaration
class MemberReality
{
public:
// This will be called when you call an instantiated
// Reality's callme() method
virtual void callme() const { std::cout << "Member Reality.\n"; }
};
// "Overriden 'virtual' member" declaration
class MemberWonderland : public MemberReality
{
public:
// This will be called when you call an instantiated
// Wonderland's Reality::callme() method
void callme() const { std::cout << "Member Wonderland.\n"; }
};
// Base class with RabbitHole
// Classes inheriting others with rabbit holes can override those holes
// by linking their ones' with the hole of the first class it inherits that
// expresses a level of this hole in question in their constructors; see
// class Wonderland below
class Reality
{
public:
void callme() // Notice this isn't virtual, but it calls
// MemberWonderland in this example: an "overriden member"
{
std::cout << "Calling from reality...\n";
// Access to the deepest hole is granted using the class'
// rabbit hole member; the first time mRabbitHole.get() is called,
// the its pointer is instantiated as its deepest linked type
mRabbitHole.get().callme();
}
protected:
Sonetto::RabbitHole<MemberReality> mRabbitHole;
};
// Derived class extending base's rabbit hole
class Wonderland : public Reality
{
public:
Wonderland()
{
// Link previous rabbit hole level with this level
// Keep in mind that this very Wonderland class could
// be inherited, so it would be wrong to call
// mRabbitHole.get() from this constructor, as it would
// instantiate MemberWonderland when it should have
// instantiated the class from the next rabbit hole level
// Because of that, as a rule, you can only access the
// rabbit hole from a constructor if you are plain sure it
// will be the deepest level of the hole
// Rabbit holes work by delaying construction of the objects
// to the moment they're needed for use (lazy initialization)
Reality::mRabbitHole.link(mRabbitHole);
}
protected:
// The first template parameter is the base pointer type
// For linkages to work, it must be the same type as the second template
// parameter passed in the previous level's template
Sonetto::RabbitHoleLevel<MemberReality,MemberWonderland> mRabbitHole;
};
int main()
{
Reality r;
Wonderland w;
r.callme(); // Prints: 'Member Reality.'
std::cout << '\n';
w.callme(); // Prints: 'Member Wonderland.'
return 0;
}
So, the core of my question is: do you personally see something wrong with this code or the pattern itself? If so, what could I do to improve it? Do you think it could've been done differently?
- 2nd edit -
I understand, guided by the answers posted here and based on boost's documentation, that I inadvertently mixed up things I've read in that documentation. The resolution of creating that weak_ptr<>
solves the problem of picking a shared_ptr<>
to the this pointer from within the object's constructor (actually, from the static factory method). Ironically, my method doesn't even solve that problem, as the rabbit hole can only be accessed after full object construction :) I've written a lot more text explaining another case in which I could use rabbit holes, but when writing it I came to the conclusion there is a much better approach, with much lesser abstractions, that needs only to separate two classes. My motivation would be that these two classes make a lot of sense (60% overall, say) being inherited, one from another, but I think it makes at least 40% to separate them. Adding the speed factor to those 40%, I'd guess it's just fine to separate them. I guess rabbit holes will have to wait for another problem to solve, but I still like them. I'm pretty sure there will be cases in which they will make a lot of sense to be used, and, thankfuly, I will know exactly how to implement (indeed, I'll have ready templates for it! (: ). I will just have to work out exception safety in it later. If I do that anytime soon, I'll edit this post so that others that find the pattern useful can pick it ready for use (btw, I hereby declare the code snippets in this post public domain; don't know if that's needed, but I'll just say it, lol).
Thanks