Main Page | Namespace List | Class Hierarchy | Compound List | File List | Namespace Members | Compound Members | File Members | Related Pages

Use of DynObj in value-based containers

As explained here, the STL uses value-based semantics that make strict ownership very difficult to use inside an STL container. However, NoPtr uses a technique that makes it possible. This section describes how to make DynObj work inside STL containers.

Use DynObj in value-based container

You want to be able to use DynObj<T> in an value-based container, such as the STL containers:

To minimize the number of classes you have to remember, NoPtr defines a "tweak" parameter that you specify when using a DynObj inside an STL container: thus was born the class DynObj<TT>::InValueContainer. You must think of this as "DynObj<TT>, inside a value-based container", rather than as a separate class. Indeed, DynObj<TT> and DynObj<TT>::InValueContainer (as well as any of the other implementations DynObj<TT>::InValueContainer*) interact seamlessly.

E.g. the following traditional code

    class Foo {
        private:
            typedef std::list<Bar*> list;
            list obj;
    };
would become
    #include "DynObj.hh"
    class Foo {
        private:
            typedef std::list<DynObj<Bar>::InValueContainer> list;
            list obj;
    };
The following member function shows seamless interaction of DynObj's in and out of containers. Building on the above example,
    class Foo {
        ...
        public:
            void f() {Bar* bar = list.front(); list.front() = NULL;}
    };
would become
    #include "DynObj.hh"
    class Foo {
        ...
        public:
            void f() {DynObj<Bar> bar( list.front().giveAway() );}
    };

Insert a DAO into an STL container

You may need to insert a DAO that already exists into a container. This is done just be calling the DynObj::giveAway() method. Alternately you may need to insert one that you are creating "on the spot". E.g. the following traditional code, showing both possibilities,
    Foo* foo1 = new Foo;
    list.push_back(foo1);
    assert(foo1 != NULL);// true
    list.push_back(new Foo);
would become
    DynObj<Foo> foo1(new Foo);
    list.push_back(foo1.giveAway());
    assert(foo1.isNull());// true
    list.push_back(makeDynObj(new Foo));
As indicated, the explicit "giveAway()" should make obvious that the original DynObj is null after the insertion takes place. The makeDynObj() is necessary because, for safety reasons, implicit construction of a DynObj from a pointer is not allowed.

Get notice if shallow-copy of container

NoPtr implicitly allows shallow copy and assign inside containers of DynObj, something made possible by notifying the compiler with ::InValueContainer. A side effect of this is that it is not possible for the compiler to prevent an inadvertent shallow copy of a container of DynObj, though shallow copy of a "free" DynObj causes a compiler error.

To minimize the chance of this happening, the DynObj contains code that asserts that any access to, or deletion of, the DAO held, is done while only one copy of the container exists. Therefore, if you access any method or member of the DAO, or it gets destroyed, while more than one copy of the container exists, you will get a failed runtime assertion of the form

assertion failed: "soleOwner()", file DynObj.hh, line N

N is not important. If you see this, find which object caused this, which container it belongs to, and when that container was copied.

Use optimizations

The DynObj<TT>::InValueContainer uses a technique to allow strict ownership inside STL containers that is fool-proof. The price to pay for this is speed: it is not as fast as it could be.

Thus was born the DynObj<TT>::InValueContainerOpt1 class (Opt1 = optimization level 1). It is an optimized version of DynObj<TT>::InValueContainer. Use it only once you know your program is working with DynObj<TT>::InValueContainer: you can then try replacing one declaration of it with DynObj<TT>::InValueContainerOpt1, redo a debug build, rerun and see if you get a failed assertion.

DynObj<TT>::InValueContainerOpt1 works for any depth of copy/assign within a container, but will fail if two independent copy/assign take place on the same DynObj, e.g. inside one of the std:: algorithms. At this point in time, I am unaware of an STL algorithm that would require this. In any case, a failed assertion (see below) after doing the change means you are limited to the fool-proof DynObj<TT>::InValueContainer.

Yet a further optimization is possible on the implementation and is called DynObj<TT>::InValueContainerOpt2. The assumption that InValueContainerOpt2 makes is that at most one-level of implicit copy/assignment will be used by the STL implementation when doing operations that use copy/assign, such as push_back, insert, etc, and std::sort etc.

The failed assertions, indicating that DynObj<TT>::InValueContainerOpt1 or 2 is insufficient and you should use the slower one, will be something like:

assertion failed: "isConnected()", file Contexts.hh, line N

or

assertion failed: "soleOwner()", file Contexts.hh, line N

Again N is unimportant.

Container of RRefs

Containers of RRefs are as simple as "std::list<RRef<Obj> >" since RRef's are copy-constructible and copy-assignable. RRef has reference semantics. E.g. copying an RRef is like copying a reference, i.e. the referrant is not copied, just the reference to it. In the world of pointers is corresponds to a shallow copy.
Generated on Mon Aug 4 18:51:33 2003 for NoPtr C++ Library by doxygen 1.3.2