Boost logo

Boost :

From: Petr Kocmid (pkocmid_at_[hidden])
Date: 2000-05-29 11:03:23


From: Dave Abrahams [mailto:abrahams_at_[hidden]]
> In what way?
> Can you please provide a quick example of usage? It's pretty hard to
> evaluate such a substantial proposal without one.

Oh yeah, that's easy! Just write: singleton_ptr<some> ;-)

Well, let me explain little background philosophy at first, then
provide some example code.

Once I needed a systematic solution for some kind of repeating
project scenarios:
- to made a common object layer for c++ wrappers managing
  third party dynamic libraries with C binding only, possibly portable
  way, ensure that library is loaded only when actually needed and
  only one time per process, having such wrapper object part of class
  hierarchy for good encapsulation

- to made object layer for some API, single instance per process. handle
  situation where available API kind and version is known at run-time only.
  Also have possibility to switch an API interface layer at run-time.

- to ensure that such 'single' objects are properly deinitialized/destroyed
  when process uses exception handling.

- idea to have a singleton object of any arbitrary type which will come
  on my mind later, declared by simple template expansion on that type
  and intermix singleton functionality with class hiearchy architecture.

It was clear that in dynamic environment using a static singleton
pattern is not a good solution at all. I made a conclusion, that it is
needed to separate following parts:
- mechanism of keeping instance uniqueness throughout the
  process (by owning an instance, only one can hold it)
- place and responsibility for instance creation and destruction
  (everybody could create and possibly destroy too, if exceptions)
- access to an instance
  (everybody can access, access may succeed or fail)

For keeping uniqueness, I decided the singleton object shall not
_be_ static type but _has_been_owned_ by some static.
I called such an instance owner singleton_factory, since it knows
also a functionality of creation and destruction of the singleton
instance: instance does not exists, until the factory is asked for it.
If asked the first time, factory creates an instance and keeps it
alive, while it is used.

This singleton_factory should also not to be creatable, because
having suddenly more that one instance of the factory will break
all the functionality to pieces.
At this point, idea of noncreatable in mood of noncopyable arised.

Now, you can briefly look at the singleton_factory template from
my previous post, but I shall continue in my graphomania now.

So, singleton_factory is a static wrapper, representing ownership.

Now, a convenient access to singleton instance is needed. Since
we desired dynamic way, pointers are handy. Since we need,
a cooperation with owner, let it be some kind of smart pointer.
I call it singleton_ptr. It has simple functionality:
- help factory to keep reference counting to know about instance
  usage.
- ask factory for instance address when dereferenced itself
  (fail if something goes wrong)

So, singleton_ptr is an accessor, not an owner.

Note a simple paradigm shift from classic smart pointers:
reference counting is kept not by pointer but by real object owner.

Now, here's an example of usage taken from simple test:
(it compiles and runs well for me with msvc6/sp3/STLport beta7
in multithreded)

#include <iostream>
#include <string>
#include <stdexcept>

using namespace std;

#include "singleton.hpp"

using namespace boost;

// let's do an example for some singleton derivation
class MySharedCore: public noncopyable {
public:
        int member1;
        
        MySharedCore() { cout << "shared core created" << endl; }
        ~MySharedCore() { cout << "shared core destroyed" << endl; }
        void method1 () { cout << "method1 called" << endl; }
        void method2 () const { cout << "method2 called" << endl; }
};

// hierarchy base class has singleton as embedded member
class MyBase: public noncopyable {
public:
        singleton_ptr<MySharedCore> shared;
        
        // we want to virtualize singleton's methods
        virtual void method1() { shared->method1(); }
        virtual void method2() const { shared->method2(); }
};

class MyDerived: public MyBase {
public:
        // just a little change for detection: swapped functionality
        virtual void method1() { shared->method2(); }
        virtual void method2() const { shared->method1(); }
};

// driver is another example of singleton kind of a class
class driver: public noncopyable {
public:
        char * s;
        int i;

        void initialize() { s = "hello"; i=1; };

        driver () { cout << "driver instance constructed" << endl; };
        ~driver () { cout << "driver instance destructed" << endl; };
};

// run the test
int main(int argc, char* argv[]) {
        singleton_ptr<driver> d;
        d->initialize();
        
        singleton_ptr<driver> c;
        // verify c and d points to the same instance
        cout << (c->i) << " " << (c->s) << endl;
        cout << (d->i) << " " << (d->s) << endl;

        // this is an example of exception handling
        try {
                // let's have plenty of them:
                singleton_ptr<driver> x [100];
                // make some illusion of bloody horror
                throw int (0);
        }
        catch (int) {
                cout << "aborting multiple usage" << endl;
                do {
                        // hold up, this is a police action!
                        singleton_factory<driver>::release();
                } while (singleton_factory<driver>::in_use());
                // oops, we killed the instance created by c and used by d
                // we are sorry for the mistake, have a nice day... ;)
        };

        // but note nothing harmful to memory with c and d happens
        // only dereferencing those ptrs will throw happily
        try {
                cout << c->s << endl;
        }
        catch (runtime_error e) {
                // somebody has stolen our instance
                cout << "error:" << e.what() << endl;
        }

        // another test: hiearchy derivation from singleton
        MyBase seno;
        // note that singleton instance of MySharedCore
            // has been created just now, transparently

        MyBase trava;
        MyDerived slama;

        seno.method1();
        trava.method1();
        slama.method1();

        // are they unique, really?
        seno.shared->member1 = 333;
        cout << trava.shared->member1 << endl;
        cout << slama.shared->member1 << endl;

        return 0;
        // MySharedCore instance is to be destroyed now,
        // within destroying the seno object
}

Here's the output of the prog:
-----------------------------------------------------------
driver instance constructed
1 hello
1 hello
aborting multiple usage
driver instance destructed
error:singleton_factory has no instance
shared core created
method1 called
method1 called
method2 called
333
333
shared core destroyed
--------------------------------------------------------------

Unbelievable, I see now the bug this test program didn't detected. Please
replace the release() implementation in my previous post with:

template<typename T> void singleton_factory<T>::release() {
        if (refcount==1) // 'tis the last one, put it away
                delete instance_ptr; // of type T
        if (refcount) refcount--; // keep zero <- was the bug
}

Here is something to discuss:
----------------------------------------------------
To avoid 'police mistakes' as in upper example, would it be convenient
to hide completely singleton_factory<> to the user? I prefer the
access to the factory, because it can be asked for instance presence
even before the first pointer (and instance itself) is created, but also
the process ending routine can destroy any remaining instance.
Also, linkage placing can be controlled much better than with
encapsulation into pointer (no problem with microsoft, but
a big complication for gnu).

Also, singleton_factory's functionality can be merged inside the
singleton_ptr. The main reson I kept them separeted was fact,
that sometime singleton instance has to be created with some
arguments. (not implemented in boost proposal for msvc member
 templates problem).

Well, a warning for singletons: beware of templated code
bloating compilers, because if suddenly having linked more
instances of singleton_factory<T>, all uniqueness is lost.
Note that having unique factory, pointers themselves could
be bloated at will without any harm.

With g++, you MUST control factory instantiation by obscure
-frepo or manage instantiation manually with -fno-implicit-templates.
Must say, comparing to code bloating stuff, msvc is a really good
bless with it's linker functionality ;-] .

Petr Kocmid
pkocmid_at_[hidden]


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk