/** * @file lllazy.h * @author Nat Goodspeed * @date 2009-01-22 * @brief Lazy instantiation of specified type. Useful in conjunction with * Michael Feathers's "Extract and Override Getter" ("Working * Effectively with Legacy Code", p. 352). * * Quoting his synopsis of steps on p.355: * * 1. Identify the object you need a getter for. * 2. Extract all of the logic needed to create the object into a getter. * 3. Replace all uses of the object with calls to the getter, and initialize * the reference that holds the object to null in all constructors. * 4. Add the first-time logic to the getter so that the object is constructed * and assigned to the reference whenever the reference is null. * 5. Subclass the class and override the getter to provide an alternative * object for testing. * * It's the second half of bullet 3 (3b, as it were) that bothers me. I find * it all too easy to imagine adding pointer initializers to all but one * constructor... the one not exercised by my tests. That suggested using * (e.g.) boost::scoped_ptr<MyObject> so you don't have to worry about * destroying it either. * * However, introducing additional machinery allows us to encapsulate bullet 4 * as well. * * $LicenseInfo:firstyear=2009&license=viewergpl$ * Copyright (c) 2009, Linden Research, Inc. * $/LicenseInfo$ */ #if ! defined(LL_LLLAZY_H) #define LL_LLLAZY_H #include <boost/function.hpp> #include <boost/scoped_ptr.hpp> #include <boost/lambda/construct.hpp> #include <stdexcept> /// LLLazyCommon simply factors out of LLLazy<T> things that don't depend on /// its template parameter. class LLLazyCommon { public: /** * This exception is thrown if you try to replace an LLLazy<T>'s factory * (or T* instance) after it already has an instance in hand. Since T * might well be stateful, we can't know the effect of silently discarding * and replacing an existing instance, so we disallow it. This facility is * intended for testing, and in a test scenario we can definitely control * that. */ struct InstanceChange: public std::runtime_error { InstanceChange(const std::string& what): std::runtime_error(what) {} }; protected: /** * InstanceChange might be appropriate in a couple of different LLLazy<T> * methods. Factor out the common logic. */ template <typename PTR> static void ensureNoInstance(const PTR& ptr) { if (ptr) { // Too late: we've already instantiated the lazy object. We don't // know whether it's stateful or not, so it's not safe to discard // the existing instance in favor of a replacement. throw InstanceChange("Too late to replace LLLazy instance"); } } }; /** * LLLazy<T> is useful when you have an outer class Outer that you're trying * to bring under unit test, that contains a data member difficult to * instantiate in a test harness. Typically the data member's class Inner has * many thorny dependencies. Feathers generally advocates "Extract and * Override Factory Method" (p. 350). But in C++, you can't call a derived * class override of a virtual method from the derived class constructor, * which limits applicability of "Extract and Override Factory Method." For * such cases Feathers presents "Extract and Override Getter" (p. 352). * * So we'll assume that your class Outer contains a member like this: * @code * Inner mInner; * @endcode * * LLLazy<Inner> can be used to replace this member. You can directly declare: * @code * LLLazy<Inner> mInner; * @endcode * and change references to mInner accordingly. * * (Alternatively, you can add a base class of the form * <tt>LLLazyBase<Inner></tt>. This is discussed further in the LLLazyBase<T> * documentation.) * * LLLazy<T> binds a <tt>boost::scoped_ptr<T></tt> and a factory functor * returning T*. You can either bind that functor explicitly or let it default * to the expression <tt>new T()</tt>. * * As long as LLLazy<T> remains unreferenced, its T remains uninstantiated. * The first time you use get(), <tt>operator*()</tt> or <tt>operator->()</tt> * it will instantiate its T and thereafter behave like a pointer to it. * * Thus, any existing reference to <tt>mInner.member</tt> should be replaced * with <tt>mInner->member</tt>. Any simple reference to @c mInner should be * replaced by <tt>*mInner</tt>. * * (If the original declaration was a pointer initialized in Outer's * constructor, e.g. <tt>Inner* mInner</tt>, so much the better. In that case * you should be able to drop in <tt>LLLazy<Inner></tt> without much change.) * * The support for "Extract and Override Getter" lies in the fact that you can * replace the factory functor -- or provide an explicit T*. Presumably this * is most useful from a test subclass -- which suggests that your @c mInner * member should be @c protected. * * Note that <tt>boost::lambda::new_ptr<T>()</tt> makes a dandy factory * functor, for either the set() method or LLLazy<T>'s constructor. If your T * requires constructor arguments, use an expression more like * <tt>boost::lambda::bind(boost::lambda::new_ptr<T>(), arg1, arg2, ...)</tt>. * * Of course the point of replacing the functor is to substitute a class that, * though referenced as Inner*, is not an Inner; presumably this is a testing * subclass of Inner (e.g. TestInner). Thus your test subclass TestOuter for * the containing class Outer will contain something like this: * @code * class TestOuter: public Outer * { * public: * TestOuter() * { * // mInner must be 'protected' rather than 'private' * mInner.set(boost::lambda::new_ptr<TestInner>()); * } * ... * }; * @endcode */ template <typename T> class LLLazy: public LLLazyCommon { public: /// Any nullary functor returning T* will work as a Factory typedef boost::function<T* ()> Factory; /// The default LLLazy constructor uses <tt>new T()</tt> as its Factory LLLazy(): mFactory(boost::lambda::new_ptr<T>()) {} /// Bind an explicit Factory functor LLLazy(const Factory& factory): mFactory(factory) {} /// Reference T, instantiating it if this is the first access const T& get() const { if (! mInstance) { // use the bound Factory functor mInstance.reset(mFactory()); } return *mInstance; } /// non-const get() T& get() { return const_cast<T&>(const_cast<const LLLazy<T>*>(this)->get()); } /// operator*() is equivalent to get() const T& operator*() const { return get(); } /// operator*() is equivalent to get() T& operator*() { return get(); } /** * operator->() must return (something resembling) T*. It's tempting to * return the underlying boost::scoped_ptr<T>, but that would require * breaking out the lazy-instantiation logic from get() into a common * private method. Assume the pointer used for operator->() access is very * short-lived. */ const T* operator->() const { return &get(); } /// non-const operator->() T* operator->() { return &get(); } /// set(Factory). This will throw InstanceChange if mInstance has already /// been set. void set(const Factory& factory) { ensureNoInstance(mInstance); mFactory = factory; } /// set(T*). This will throw InstanceChange if mInstance has already been /// set. void set(T* instance) { ensureNoInstance(mInstance); mInstance.reset(instance); } private: Factory mFactory; // Consider an LLLazy<T> member of a class we're accessing by const // reference. We want to allow even const methods to touch the LLLazy<T> // member. Hence the actual pointer must be mutable because such access // might assign it. mutable boost::scoped_ptr<T> mInstance; }; #if (! defined(__GNUC__)) || (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ > 3) // Not gcc at all, or a gcc more recent than gcc 3.3 #define GCC33 0 #else #define GCC33 1 #endif /** * LLLazyBase<T> wraps LLLazy<T>, giving you an alternative way to replace * <tt>Inner mInner;</tt>. Instead of coding <tt>LLLazy<Inner> mInner</tt>, * you can add LLLazyBase<Inner> to your Outer class's bases, e.g.: * @code * class Outer: public LLLazyBase<Inner> * { * ... * }; * @endcode * * This gives you @c public get() and @c protected set() methods without * having to make your LLLazy<Inner> member @c protected. The tradeoff is that * you must access the wrapped LLLazy<Inner> using get() and set() rather than * with <tt>operator*()</tt> or <tt>operator->()</tt>. * * This mechanism can be used for more than one member, but only if they're of * different types. That is, you can replace: * @code * DifficultClass mDifficult; * AwkwardType mAwkward; * @endcode * with: * @code * class Outer: public LLLazyBase<DifficultClass>, public LLLazyBase<AwkwardType> * { * ... * }; * @endcode * but for a situation like this: * @code * DifficultClass mMainDifficult, mAuxDifficult; * @endcode * you should directly embed LLLazy<DifficultClass> (q.v.). * * For multiple LLLazyBase bases, e.g. the <tt>LLLazyBase<DifficultClass>, * LLLazyBase<AwkwardType></tt> example above, access the relevant get()/set() * as (e.g.) <tt>LLLazyBase<DifficultClass>::get()</tt>. (This is why you * can't have multiple LLLazyBase<T> of the same T.) For a bit of syntactic * sugar, please see getLazy()/setLazy(). */ template <typename T> class LLLazyBase { public: /// invoke default LLLazy constructor LLLazyBase() {} /// make wrapped LLLazy bind an explicit Factory LLLazyBase(const typename LLLazy<T>::Factory& factory): mInstance(factory) {} /// access to LLLazy::get() T& get() { return *mInstance; } /// access to LLLazy::get() const T& get() const { return *mInstance; } protected: // see getLazy()/setLazy() #if (! GCC33) template <typename T2, class MYCLASS> friend T2& getLazy(MYCLASS* this_); template <typename T2, class MYCLASS> friend const T2& getLazy(const MYCLASS* this_); #else // gcc 3.3 template <typename T2, class MYCLASS> friend T2& getLazy(const MYCLASS* this_); #endif // gcc 3.3 template <typename T2, class MYCLASS> friend void setLazy(MYCLASS* this_, T2* instance); template <typename T2, class MYCLASS> friend void setLazy(MYCLASS* this_, const typename LLLazy<T2>::Factory& factory); /// access to LLLazy::set(Factory) void set(const typename LLLazy<T>::Factory& factory) { mInstance.set(factory); } /// access to LLLazy::set(T*) void set(T* instance) { mInstance.set(instance); } private: LLLazy<T> mInstance; }; /** * @name getLazy()/setLazy() * Suppose you have something like the following: * @code * class Outer: public LLLazyBase<DifficultClass>, public LLLazyBase<AwkwardType> * { * ... * }; * @endcode * * Your methods can reference the @c DifficultClass instance using * <tt>LLLazyBase<DifficultClass>::get()</tt>, which is admittedly a bit ugly. * Alternatively, you can write <tt>getLazy<DifficultClass>(this)</tt>, which * is somewhat more straightforward to read. * * Similarly, * @code * LLLazyBase<DifficultClass>::set(new TestDifficultClass()); * @endcode * could instead be written: * @code * setLazy<DifficultClass>(this, new TestDifficultClass()); * @endcode * * @note * I wanted to provide getLazy() and setLazy() without explicitly passing @c * this. That would imply making them methods on a base class rather than free * functions. But if <tt>LLLazyBase<T></tt> derives normally from (say) @c * LLLazyGrandBase providing those methods, then unqualified getLazy() would * be ambiguous: you'd have to write <tt>LLLazyBase<T>::getLazy<T>()</tt>, * which is even uglier than <tt>LLLazyBase<T>::get()</tt>, and therefore * pointless. You can make the compiler not care which @c LLLazyGrandBase * instance you're talking about by making @c LLLazyGrandBase a @c virtual * base class of @c LLLazyBase. But in that case, * <tt>LLLazyGrandBase::getLazy<T>()</tt> can't access * <tt>LLLazyBase<T>::get()</tt>! * * We want <tt>getLazy<T>()</tt> to access <tt>LLLazyBase<T>::get()</tt> as if * in the lexical context of some subclass method. Ironically, free functions * let us do that better than methods on a @c virtual base class -- but that * implies passing @c this explicitly. So be it. */ //@{ #if (! GCC33) template <typename T, class MYCLASS> T& getLazy(MYCLASS* this_) { return this_->LLLazyBase<T>::get(); } template <typename T, class MYCLASS> const T& getLazy(const MYCLASS* this_) { return this_->LLLazyBase<T>::get(); } #else // gcc 3.3 // For const-correctness, we really should have two getLazy() variants: one // accepting const MYCLASS* and returning const T&, the other accepting // non-const MYCLASS* and returning non-const T&. This works fine on the Mac // (gcc 4.0.1) and Windows (MSVC 8.0), but fails on our Linux 32-bit Debian // Sarge stations (gcc 3.3.5). Since I really don't know how to beat that aging // compiler over the head to make it do the right thing, I'm going to have to // move forward with the wrong thing: a single getLazy() function that accepts // const MYCLASS* and returns non-const T&. template <typename T, class MYCLASS> T& getLazy(const MYCLASS* this_) { return const_cast<MYCLASS*>(this_)->LLLazyBase<T>::get(); } #endif // gcc 3.3 template <typename T, class MYCLASS> void setLazy(MYCLASS* this_, T* instance) { this_->LLLazyBase<T>::set(instance); } template <typename T, class MYCLASS> void setLazy(MYCLASS* this_, const typename LLLazy<T>::Factory& factory) { this_->LLLazyBase<T>::set(factory); } //@} #endif /* ! defined(LL_LLLAZY_H) */