|
Boost : |
From: Rob Stewart (stewart_at_[hidden])
Date: 2005-03-26 15:08:08
> DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=beta; d=gmail.com;
> h=received:message-id:date:from:reply-to:to:subject:in-reply-to:mime-version:content-type:content-transfer-encoding:references;
> b=MZ2/STCvbocBJRKdRXXSdw8PEyGA45EQ1ZT81RfX2GtTF/tK2gXcyhr9okaGWB5dxhggJkBvr8eZCj4/GF+w/HM25bcs9e6kJI5arrFDY7nZi8UV747ffQUJBAUo/TykzCLAUq5VbtTDIuXp2khe/tV/B5NiFg7Wnz4ag9A2+FM=
From: Dan Rosen <dan.rosen_at_[hidden]>
>
> > All that said, there's still a flaw in your approach, which I've
> > inherited in mine. That is: neither of our singleton<> class templates
> > actually force clients to derive from their intended bases
> > (singleton_base in your case, and noninstantiable in mine).
>
> Okay, I figured out a way to make this work. It's terrible and I hate
> it, and I think the simple approach (private ctor/dtor) is best, but I
> feel like I have to post my solution for geeky completeness. Anyway,
> here it is:
>
> template <typename T> class noninstantiable {
> protected:
> virtual void myob () = 0;
> };
> template <typename T> void noninstantiable<T>::myob () { }
>
> template <typename T> class instantiable : public T {
> private:
> virtual void myob () { this->noninstantiable<T>::myob(); }
> };
>
> If this isn't self-explanatory, what's going on here is that
> noninstantiable<> is basically the same as before, but instantiable<>
> forces its clients to derive from noninstantiable by referring to its
> myob() member directly. So there are four scenarios:
>
> class A { };
> class B : public noninstantiable<B> { };
> typedef instantiable<A> C;
> typedef instantiable<B> D;
>
> A a; // fine
> B b; // error: myob not implemented
> C c; // error: doesn't derive from noninstantiable
> D d; // fine
>
> This still doesn't allow clients to write literally zero code, but it
> does close the loophole of case C above. Again, though, you'll have a
> hard time convincing me that this is even marginally better than just
> having a private ctor/dtor and explicitly declaring the friends who
> you want to allow instantiation privileges to. This approach is less
> safe, has more overhead, and is arcane.
Let me address a couple of points raised. First, assuming what
you've outlined is an acceptable approach, what you've called
noninstantiable should be called singleton_base so that clients
derive from singleton_base. (Adjust the other names similarly.)
The next point is whether requiring private ctor/dtor is better
than using a noninstantiable scheme (not necessarily the one
shown above). The OP's goal is to prevent instantiation of a
type as both a Singleton and otherwise. If you accept that goal,
then you want to prevent mistakes and a noninstantiable scheme is
needed. Otherwise, it is easy to forget to Do The Right Thing
(tm) and ensure your class can't be both a Singleton and not. I
prefer to have the compiler catch mistakes and, if you accept the
stated goal, this is a mistake that the compiler can catch.
The question is whether the goal is acceptable. Consider a
printer Singleton. There may be one, default printer, and that
can be captured quite handily via a Singleton. The question is:
Can there be other printers? Obviously, there can, so preventing
instantiation of printer outside the Singleton framework is
overkill.
I contend that you can provide a mechanism whereby clients can
ensure their type can only be instantiated by the Singleton
framework, but don't require that. IOW, singleton_base could be
used to prevent non-Singleton instantiation, but make its use
optional. (Making it optional also prevents MI problems for
types that already derive from other classes.)
Thus, I think the framework should permit singleton<A> and
singleton<B> (where noninstantiable is called singleton_base).
-- Rob Stewart stewart_at_[hidden] Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk