Topic: Comments on TR1 smart pointers


Author: brangdon@cix.co.uk (Dave Harris)
Date: Thu, 24 Jul 2003 02:11:35 +0000 (UTC)
Raw View
pdimov@mmltd.net (Peter Dimov) wrote (abridged):
> With use_count(), assuming a typical reference counted implementation,
> the compiler must increment __use_count by 2 and not one.

That was the trick I was missing, thanks.

-- Dave Harris, Nottingham, UK

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Mon, 21 Jul 2003 18:31:16 +0000 (UTC)
Raw View
terekhov@web.de (Alexander Terekhov) wrote in message news:<3F17EF6F.97295E7D@web.de>...
> Peter Dimov wrote:
> [...]
> > This approach is about 20-25% faster than the "two count atomic" case,
> > and about 20-25% slower than the single threaded case.
>
> You might also want to try something along the lines of: < this
> version supersedes "sp_counted_base" stuff that I've posted in my
> previous "Nah....Oder" message in this thread here >

[...]

For the record, the numbers above are for

  http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp

that uses atomic increment and decrement operations where possible
instead of cmpxchg.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: terekhov@web.de (Alexander Terekhov)
Date: Tue, 22 Jul 2003 02:01:06 +0000 (UTC)
Raw View
Peter Dimov wrote:
>
> terekhov@web.de (Alexander Terekhov) wrote in message news:<3F17EF6F.97295E7D@web.de>...
> > Peter Dimov wrote:
> > [...]
> > > This approach is about 20-25% faster than the "two count atomic" case,
> > > and about 20-25% slower than the single threaded case.
> >
> > You might also want to try something along the lines of: < this
> > version supersedes "sp_counted_base" stuff that I've posted in my
> > previous "Nah....Oder" message in this thread here >
>
> [...]
>
> For the record, the numbers above are for
>
>   http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp
>
> that uses atomic increment and decrement operations

with or without the "lock" prefix? The "lock" prefix is needed on
SMP and/or HT systems (IA32, that is). I'm just curious... ;-) ;-)

regards,
alexander.

--
Unix(R) and UnixWare(R) are registered trademarks of The Open Group
in the United States and other countries. OTOH, "OPENSERVER" is the
ABANDONED (dead) trademark in the United States. In other news, all
SCO operating systems (if any/and etc.) are dead in other countries
and the United States. Please visit <http://www.sco.de>.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Tue, 22 Jul 2003 18:46:49 +0000 (UTC)
Raw View
Alexander Terekhov wrote:
> Peter Dimov wrote:
> >
> > For the record, the numbers above are for
> >
> >   http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp
> >
> > that uses atomic increment and decrement operations
>
> with or without the "lock" prefix? The "lock" prefix is needed on
> SMP and/or HT systems (IA32, that is). I'm just curious... ;-) ;-)

The code in the file linked above uses the Windows
Interlocked* functions, which on my Windows box (Win2k,
dual PIII [SMP]) appear to use the lock prefix.

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Tue, 22 Jul 2003 18:47:10 +0000 (UTC)
Raw View
terekhov@web.de (Alexander Terekhov) wrote in message news:<3F1C364D.C9CE338E@web.de>...
> Peter Dimov wrote:
> >
> > terekhov@web.de (Alexander Terekhov) wrote in message news:<3F17EF6F.97295E7D@web.de>...
> > > Peter Dimov wrote:
> > > [...]
> > > > This approach is about 20-25% faster than the "two count atomic" case,
> > > > and about 20-25% slower than the single threaded case.
> > >
> > > You might also want to try something along the lines of: < this
> > > version supersedes "sp_counted_base" stuff that I've posted in my
> > > previous "Nah....Oder" message in this thread here >
> >
> > [...]
> >
> > For the record, the numbers above are for
> >
> >   http://www.pdimov.com/cpp/shared_count_x86_exp2.hpp
> >
> > that uses atomic increment and decrement operations
>
> with or without the "lock" prefix? The "lock" prefix is needed on
> SMP and/or HT systems (IA32, that is). I'm just curious... ;-) ;-)

With lock. _InterlockedIncrement/Decrement translate to "lock xadd".
_InterlockedCompareExchange translates to "lock cmpxchg". This is on
AMD Athlon; the numbers for a Pentium 4 may be different.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: terekhov@web.de (Alexander Terekhov)
Date: Fri, 18 Jul 2003 20:29:29 +0000 (UTC)
Raw View
Ben Hutchings wrote:
[...]
> I shall attempt to translate this to fit into the Boost implementation,
> using the following platform-dependent functions: ...

Nah.

class sp_counted_base {
  /* ... */
  refcount<std::size_t, basic> use_count_;
  refcount<std::size_t, basic> self_count_;
  /* ... */
public:
  /* ... */
  sp_counted_base() : use_count_(1), self_count_(1) { }

  std::size_t use_count() const throw() {
    return use_count_.get();
  }

  void add_ref() throw() {
    use_count_.increment();
  }

  bool lock() throw() {
    return use_count_.increment_if_not_min();
  }

  void weak_add_ref() throw() {
    self_count_.increment();
  }

  void weak_release() throw() {
    if (!self_count_.decrement(msync::acq))
      destruct();
  }

  void release() throw() {
    if (!use_count_.decrement()) {
      dispose();
      if (!self_count_.decrement(msync::rel))
        destruct();
    }
  }
  /* ... */
};

http://www.terekhov.de/pthread_refcount_t/experimental/refcount.cpp

Oder?

regards,
alexander.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: terekhov@web.de (Alexander Terekhov)
Date: Fri, 18 Jul 2003 22:34:30 +0000 (UTC)
Raw View
Peter Dimov wrote:
[...]
> This approach is about 20-25% faster than the "two count atomic" case,
> and about 20-25% slower than the single threaded case.

You might also want to try something along the lines of: < this
version supersedes "sp_counted_base" stuff that I've posted in my
previous "Nah....Oder" message in this thread here >

class sp_counted_base {
  /* ... */
  typedef refcount<std::size_t, basic> count;
  count use_count_, self_count_;
  /* ... */
public:
  /* ... */
  sp_counted_base() : use_count_(1), self_count_(1) { }

  std::size_t use_count() const throw() {
    return use_count_.get();
  }

  void add_ref() throw() {
    use_count_.increment();
  }

  bool lock() throw() {
    return use_count_.increment_if_not_min();
  }

  void weak_add_ref() throw() {
    self_count_.increment();
  }

  void weak_release() throw() {
    if (!self_count_.decrement(msync::acq, count::may_not_store_min))
      destruct();
  }

  void release() throw() {
    if (!use_count_.decrement()) {
      dispose();
      if (!self_count_.decrement(msync::rel, count::may_not_store_min))
        destruct();
    }
  }
  /* ... */
};

http://www.terekhov.de/pthread_refcount_t/experimental/refcount.cpp
(updated recently)

Oder?

regards,
alexander.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: markus.mauhart@nospamm.chello.at ("Markus Mauhart")
Date: Sat, 19 Jul 2003 06:20:53 +0000 (UTC)
Raw View
"Alexander Terekhov" <terekhov@web.de> wrote ...
>
> Nah.

also Nah.

> class sp_counted_base {
>   /* ... */
>   refcount<std::size_t, basic> use_count_;
>   refcount<std::size_t, basic> self_count_;
>   /* ... */
> public:
>   /* ... */
>   sp_counted_base() : use_count_(1), self_count_(1) { }
>

>   void release() throw() {
>     if (!use_count_.decrement()) {
>       dispose();
>       if (!self_count_.decrement(msync::rel))
>         destruct();
>     }
>   }

>   void add_ref() throw() {
>     use_count_.increment();
>   }

.... missing something w.r.t. symmetry ?-)))


>   /* ... */
> };


Regards,
Markus.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: markus.mauhart@nospamm.chello.at ("Markus Mauhart")
Date: Mon, 21 Jul 2003 18:30:59 +0000 (UTC)
Raw View
""Markus Mauhart"" <markus.mauhart@nospamm.chello.at> wrote ...
> "Alexander Terekhov" <terekhov@web.de> wrote ...
> >
> >   void release() throw() {
> >     if (!use_count_.decrement()) {
> >       dispose();
> >       if (!self_count_.decrement(msync::rel))
> >         destruct();
> >     }
> >   }
>
> >   void add_ref() throw() {
> >     use_count_.increment();
> >   }
>
> .... missing something w.r.t. symmetry ?-)))

forget this one Alexander, I missed your (un-symmetric;-) braces
late in the evening, hence I parsed ...

> >     if (!use_count_.decrement()) {
> >       dispose();
> >       if (!self_count_.decrement(msync::rel))
> >         destruct();
> >     }

.... as if it was ...

     if (!use_count_.decrement())
       dispose();
       if (!self_count_.decrement(msync::rel))
         destruct();

Regards,
Markus.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: cpdaniel@nospam.mvps.org ("Carl Daniel")
Date: Sat, 12 Jul 2003 19:45:03 +0000 (UTC)
Raw View
David Abrahams wrote:
> cpdaniel@nospam.mvps.org ("Carl Daniel") writes:
>> Perhaps I'm missing something obvious, but the use of a mutex to
>> provide MT safety in boost::shared_ptr guarantees that it is in fact
>> far less efficient than typical COM reference counting, including
>> the virtual function call overhead.
>
> Of course, a mutex is only used if multithreading is enabled.

In my experience, nearly all interesting programs that aren't command-line
tools are multi-threaded, so a significant cost, that "only" appears in the
MT version is a significant cost.

>
>> If shared_ptr was updated to use InterlockedIncrement, then it could
>> be faster than the typical COM AddRef() call due to the virtual
>> function call in the latter.
>
> Which shows that the cost is a "mere" implementation detail.  Care to
> submit a patch?

See Richard Smith's reply to my posting.  I hadn't looked through shared_ptr
in a while, but he's correct - it's updating two counts.  To atomixally
increment two counts requires a mutex (or equivalent).  If the two counts do
need to be updated atomically, it's doomed to be 100's to 1000's of times
slower than the ST version of the same operation.  It's unfortunate that
weak_ptr has such a significant cost.  Perhaps it's not actually necessary
that both counts be incremented atomically - I haven't studied the code
enough to say.

>
>> Worse, COM objects "know" how their reference counts should be
>> maintained.  A COM object that's "single threaded"
>
> Well isn't it actually ``a COM _type_ that's "single threaded"?''

Yes, that's correct.  In typical COM programming, it's unusual to have more
than a single instance of a particular type, but it does happen.

>> Thus, for a very common case,
>> the boost::shared_ptr mechanism is nearly guaranteed to be less
>> efficient than normal COM reference counting.
>
> That's an interesting point.  Of course, COM's apartment model is
> quite problematic in many cases, and having to decide
> single/multi-threadedness per-type instead of per-object can be a
> problem too, can't it?

Yes, it can.   Mostly a problem for the consumer - choosing to make a
component single threaded is usually a concession to the developer of the
component, since (s)he may be working in a language (such as Visual Basic)
that supports COM development but doesn't effectively support
multi-threading.

-cd

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Sat, 12 Jul 2003 19:45:20 +0000 (UTC)
Raw View
In article <%iUPa.1119$tE5.126110827@newssvr13.news.prodigy.com>, Carl
Daniel <cpdaniel@nospam.mvps.org> wrote:

| The core issue, IMO, is not even about COM, but the fact that in an MT-safe
| version, boost::shared_ptr uses a mutex to protect the atomic increment of
| not one but two counts (see Richard Smith's reply to my posting).  This
| means that copying a boost::shared_ptr in an MT program on Windows is
| somewhere between 100's to 1000's of times slower than copying a plain
| pointer.  I'm sure the cost on a pthreads system will be similarly high.
| IMO, that's far too high a price to pay, regardless of whether you're using
| it with COM.

This is drifting from the subject of the original post somewhat, but I
have been questioning the value of a "MT-safe" shared_ptr.  I recently
used shared_ptr in an MT environment as part of the implementation of a
larger object.  In this particular application I found it better to put
the synchronization primitive at a higher level than shared_ptr's
counts.  And once placed at that higher level, an additional mutex (or
whatever) for shared_ptr's count was just an expensive redundancy.

--
Howard Hinnant
Metrowerks

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: eldiener@earthlink.net ("Edward Diener")
Date: Sun, 13 Jul 2003 06:00:52 +0000 (UTC)
Raw View
"Carl Daniel" wrote:
> "Edward Diener" wrote:
>> "Carl Daniel" wrote:
>>> Perhaps I'm missing something obvious, but the use of a mutex to
>>> provide MT safety in boost::shared_ptr guarantees that it is in fact
>>> far less efficient than typical COM reference counting, including
>>> the virtual function call overhead.  If shared_ptr was updated to
>>> use InterlockedIncrement, then it could be faster than the typical
>>> COM AddRef() call due to the virtual function call in the latter.
>>
>> Perhaps you are missing the fact that other operating systems other
>> than Windows exists, and that InterlockedIncrement is not a C++
>> Standard library function.
>>
>
> No, not missing that at all.  Merely commenting on a comment about
> shared_ptr and COM.  The fact that COM doesn't exist on many OS's in
> no way detracts from the discussion.

But it does make it so much easier to say something good about how COM does
something as opposed to a multi-OS paradigm such as shared_ptr.

> The fact that
> InterlockedIncrement is not a standard library function is similarly
> irrelevant:  the entire boost::thread library, from which the mutex
> is obtained, is built on non-standard library functions (which
> follows naturally, since there are no standard library functions for
> threading/thread safety).

Agreed.

My point was that to use atomic operations to avoid a mutex involves a lower
level approach for each operating system on which Boost is supported. I have
no doubt this can be done, and probably done best by creating a templated
atomic change which has to be coded differently for each OS. Mutexes are at
a slightly higher level, but your point is well-taken.

>
> The core issue, IMO, is not even about COM, but the fact that in an
> MT-safe version, boost::shared_ptr uses a mutex to protect the atomic
> increment of not one but two counts (see Richard Smith's reply to my
> posting).  This means that copying a boost::shared_ptr in an MT
> program on Windows is somewhere between 100's to 1000's of times
> slower than copying a plain pointer.  I'm sure the cost on a pthreads
> system will be similarly high. IMO, that's far too high a price to
> pay, regardless of whether you're using it with COM.

I don't see it as too high a price to pay because such a belief is purely
relative. Unless I am copying a very large number of shared_ptrs back and
forth, I probably won't even notice it on today's high-powered CPUs.

You have a good point that perhaps Boost needs to have cross-OS atomic
change functionality for those cases where it exists and use a mutex where
it does not.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Sun, 13 Jul 2003 15:56:54 +0000 (UTC)
Raw View
cpdaniel@nospam.mvps.org ("Carl Daniel") wrote in message news:<%iUPa.1119$tE5.126110827@newssvr13.news.prodigy.com>...
>
> The core issue, IMO, is not even about COM, but the fact that in an MT-safe
> version, boost::shared_ptr uses a mutex to protect the atomic increment of
> not one but two counts (see Richard Smith's reply to my posting).  This
> means that copying a boost::shared_ptr in an MT program on Windows is
> somewhere between 100's to 1000's of times slower than copying a plain
> pointer.  I'm sure the cost on a pthreads system will be similarly high.
> IMO, that's far too high a price to pay, regardless of whether you're using
> it with COM.

Your numbers are wrong by a factor of between 13 and 229, another way
of saying that the real win32-MT boost::shared_ptr is 4.37 to 7.6
times slower to copy than a plain pointer according to my
measurements. (*) Feel free to verify my numbers independently and
post the results if there is a significant difference. I wonder why
you consider the comparison meaningful, though.

Taking the time to measure before speculating is well worth the
investment, IMO.

(*) http://www.boost.org/libs/smart_ptr/test/shared_ptr_timing_test.cpp

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Sun, 13 Jul 2003 15:57:02 +0000 (UTC)
Raw View
Carl Daniel wrote:

> If the two counts do
> need to be updated atomically, it's doomed to be 100's to 1000's of times
> slower than the ST version of the same operation.

Let's not be too pesimistic -- it's certainly not *that*
bad.  Using this test code I posted earlier in this thread:

  struct X { double w,x,y,z; };
  void foo( boost::shared_ptr<X> ) {}

  int main() {
    timer t;
    for ( int i(0), n(int(1E6)); i<n; ++i ) {
      boost::shared_ptr<X> x(new X);
      foo(x); foo(x);
    }

    std::cout << std::setprecision(3) << t.get_time() << "s\n";
  }

compiled an run on Linux (with threading via pthreads) by

  [richard@verdi test]$ g++-3.2.1 smptr.cpp -O2 -g -osmptr \
    -I /usr/local/include/boost_1_30/
  [richard@verdi test]$ N=20; for ((i=0;$i<$N;++i)); do \
    ./smptr; done | awk '{s+=$1} END{printf("%.3f\n", s/'$N')}'

I profiled the singled-threaded version at 0.781 and the
multi-threaded version at 0.967.  This corresponds to a 23%
increase in run time.

Repeating with a main() changed to read:

  int main() {
    timer t;
    boost::shared_ptr<X> src(new X);
    for ( int i(0), n(int(1E6)); i<n; ++i ) {
      boost::shared_ptr<X> x(src);
      foo(x); foo(x);
    }

    std::cout << std::setprecision(3) << t.get_time() << "s\n";
  }

I get values of 0.077 (single-threaded) versus 0.221
(multi-threaded), corresponding to a 187% increase in run
time.

> Perhaps it's not actually necessary
> that both counts be incremented atomically - I haven't studied the code
> enough to say.

It is possible to use standard atomic increment / decrements
to on the weak count, which would improve efficiency
somewhat.  (This is not an optimisation that Boost currently
does.)  With the existing mechanism for creating shared_ptrs
from weak_ptrs, I can't see how to avoid the mutex.  I've
discussed this in more detail in response to Alexander
Terekhov's post in this thread.

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Sun, 13 Jul 2003 17:49:57 +0000 (UTC)
Raw View
Alexander Terekhov wrote:

> You don't really need "atomic operations to manipulate a PAIR
> of variable simulatenously".
>
> http://terekhov.de/pthread_refcount_t/experimental/refcount.cpp
> http://groups.google.com/groups?selm=3EC0F1F1.B78AA0DA%40web.de
> (Subject: Re: Atomic ops on Intel: do they sync?)

This is very interesting article -- thank you for pointing
me to it.

I spent quite a while last night trying to work out how to
do it without a mutex, and got everything working except the
mechansim for converting a weak_ptr to a shared_ptr:  i.e.
the following two functions

  shared_ptr<T> weak_ptr<T>::lock()
  shared_ptr<T>::shared_ptr(weak_ptr<T> const& )

Quoting from your mail, this is done via the following
function:

  bool refs::aquire_strong_from_weak() {
    int status = pthread_refcount_increment_positive
                   ( &strong_count );
    if (PTHREAD_REFCOUNT_DROPPED_TO_ZERO == status)
      return false;
    return true.
  }

The pthread_refcount_increment_positive function is part of
your own proposed extention to POSIX.  It does the following
in an atomic manner:

  int pthread_refcount_increment_positive
    ( pthread_refcount_t *rc )
  {
    if ( *rc == 0 )
      return PTHREAD_REFCOUNT_DROPPED_TO_ZERO;
    ++*rc;
    return 0
  }

Which is precisely what is needed in this situation.  My
concern, however, is how this can be implemented.  Looking
at your implementation at

  http://www.terekhov.de/pthread_refcount_t/poor-man/beta2/prefcnt.c

I notice you use a mutex to ensure syncronisation in all
the atomic functions, so we're back to square one -- all
we've succeeded in doing is moving the mutex from the
smart pointer library to the pthread_refcount library.

Atomic operations such as (++*rc) and (--*rc == 0) can often
be implemented using the atomic operations of the underlying
architecture.  For example, Intel's x86 series of processors
[where x >= 4] allow the use of the LOCK prefix to
instructions such as INC and DEC, thus using hand-crafted
machine code, it is possible to write very efficient atomic
increment / decrement functions.  Windows supplies
Interlocked{Increment, Decrement} functions that do
precisely this.

Unfortunately, I'm not aware of any similar way to implement
your pthread_refcount_increment_positive function.  This is
the problem, and means that the TR1 smart pointer library
violates the zero-overhead principle:  in threaded code, you
cannot use shared_ptr without paying a overhead for weak_ptr
support, even when you do not use weak_ptr.  How large that
overhead is, and whether it's a price worth paying is
another question.

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Mon, 14 Jul 2003 02:58:24 +0000 (UTC)
Raw View
cpdaniel@nospam.mvps.org ("Carl Daniel") writes:

> Perhaps it's not actually necessary that both counts be incremented
> atomically - I haven't studied the code enough to say.

richard@ex-parrot.com (Richard Smith) writes:

> Unfortunately, I'm not aware of any similar way to implement
> your pthread_refcount_increment_positive function.  This is
> the problem, and means that the TR1 smart pointer library
> violates the zero-overhead principle:  in threaded code, you
> cannot use shared_ptr without paying a overhead for weak_ptr
> support, even when you do not use weak_ptr.  How large that
> overhead is, and whether it's a price worth paying is
> another question.

Well, I think it's easy to get zero-overhead shared_ptr copying back.
Right now we have "cheap" weak_ptr copies and "expensive" shared_ptr
copies.  Shouldn't the balance be pushed in the other direction?

I might be missing something, but it seems to me that if, instead of
storing use_count, you store the difference between weak_count and
use_count, shared_ptr can be copied with an atomic increment operation
on the weak_count.

Is there a hidden race condition lurking here?  I don't see one.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: cpdaniel@nospam.mvps.org ("Carl Daniel")
Date: Mon, 14 Jul 2003 05:34:33 +0000 (UTC)
Raw View
Peter Dimov wrote:

>
> Your numbers are wrong by a factor of between 13 and 229, another way
> of saying that the real win32-MT boost::shared_ptr is 4.37 to 7.6
> times slower to copy than a plain pointer according to my
> measurements. (*) Feel free to verify my numbers independently and
> post the results if there is a significant difference. I wonder why
> you consider the comparison meaningful, though.
>
> Taking the time to measure before speculating is well worth the
> investment, IMO.

Yep.  My bad.  I committed the cardinal sin of speculating based on
incomplete and inaccurate information - my appologies.

I was thrown aback by the use of the name "mutex", when in fact it doesn't
map to an OS mutex at all, but a home-grown spin-lock or CriticalSection,
depending on some #defines and the platform.  My re-running of your test
confirms about a 7x performance cost.

-cd



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Mon, 14 Jul 2003 17:41:41 +0000 (UTC)
Raw View
richard@ex-parrot.com (Richard Smith) wrote in message news:<Pine.LNX.4.55.0307131111550.15138@sphinx.mythic-beasts.com>...
> Alexander Terekhov wrote:
>
> > You don't really need "atomic operations to manipulate a PAIR
> > of variable simulatenously".
> >
> > http://terekhov.de/pthread_refcount_t/experimental/refcount.cpp
> > http://groups.google.com/groups?selm=3EC0F1F1.B78AA0DA%40web.de
> > (Subject: Re: Atomic ops on Intel: do they sync?)
>
> This is very interesting article -- thank you for pointing
> me to it.

[...]

>   int pthread_refcount_increment_positive
>     ( pthread_refcount_t *rc )
>   {
>     if ( *rc == 0 )
>       return PTHREAD_REFCOUNT_DROPPED_TO_ZERO;
>     ++*rc;
>     return 0
>   }
>
> Which is precisely what is needed in this situation.  My
> concern, however, is how this can be implemented.

int pthread_refcount_increment_positive( long * rc )
{
    long tmp;

    do
    {
        tmp = *rc;
        if(tmp == 0) return PTHREAD_REFCOUNT_DROPPED_TO_ZERO;
    }
    while( InterlockedCompareExchange(rc, tmp + 1, tmp) != tmp );

    return 0;
}

I think.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: petebecker@acm.org (Pete Becker)
Date: Mon, 14 Jul 2003 21:55:47 +0000 (UTC)
Raw View
Howard Hinnant wrote:
>
> This is drifting from the subject of the original post somewhat, but I
> have been questioning the value of a "MT-safe" shared_ptr.  I recently
> used shared_ptr in an MT environment as part of the implementation of a
> larger object.  In this particular application I found it better to put
> the synchronization primitive at a higher level than shared_ptr's
> counts.  And once placed at that higher level, an additional mutex (or
> whatever) for shared_ptr's count was just an expensive redundancy.
>

In another context, locking inside a stream inserter or extractor
doesn't help multi-threaded code, because it doesn't guarantee that
multiple insertions will operate atomically. So the application has to
add locks around all blocks io operations to the same stream, and the
locks in the inserters and extractors are then redundant.

Thread safety is a design issue. Locks (including locks in smart
pointers and stream operations) are tools. Putting them inside a library
is usually the wrong thing -- too low level. They protect the library
from corruption, but that's not really a problem that ought to be
solved, because the application still won't be correct. A correctly
written multi-threaded application typically won't need those low level
locks.

--

"To delight in war is a merit in the soldier,
a dangerous quality in the captain, and a
positive crime in the statesman."
 George Santayana

"Bring them on."
 George W. Bush

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: andys@despammed.com (Andy Sawyer)
Date: Mon, 14 Jul 2003 21:56:11 +0000 (UTC)
Raw View
In article <%qUPa.1120$rs5.125506698@newssvr13.news.prodigy.com>,
 on Sat, 12 Jul 2003 19:45:03 +0000 (UTC),
 cpdaniel@nospam.mvps.org ("Carl Daniel") wrote:

> In my experience, nearly all interesting programs that aren't
> command-line tools are multi-threaded, so a significant cost, that
> "only" appears in the MT version is a significant cost.

Quake isn't multi-threaded - I found that pretty interesting :).

More significantly, I also worked in a financial trading exchange
(again, pretty interesting) where the main trade-matching program
(i.e. the heart of the exchange's whole business) was single-threaded
(primarily for legal reasons, apparently). In fact, although it ran on a
multi-processor machine, the requirements were that this application
only ever executed on a single, specified CPU. These examples may be
considered corner cases, but they do illustrate the fact that there are
plenty of complex applications which are still single
threaded. (admittedly, only one of my examples runs on Windows ;))

> > Well isn't it actually ``a COM _type_ that's "single threaded"?''
>
> Yes, that's correct.  In typical COM programming, it's unusual to have
> more than a single instance of a particular type, but it does happen.

In my experience, it happens often enough that I wouldn't consider it at
all 'unusual'. I've worked in envrionments where there are literally
thousands of instances of a single type at any given moment.

Regards,
 Andy S.
--
"Light thinks it travels faster than anything but it is wrong. No matter
 how fast light travels it finds the darkness has always got there first,
 and is waiting for it."                  -- Terry Pratchett, Reaper Man

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 14 Jul 2003 21:56:32 +0000 (UTC)
Raw View
dave@boost-consulting.com (David Abrahams) wrote (abridged):
> That's an interesting point.  Of course, COM's apartment model is
> quite problematic in many cases, and having to decide
> single/multi-threadedness per-type instead of per-object can be a
> problem too, can't it?

Deciding per-type is less of a problem than deciding per-program, as the
current shared_ptr<> proposal requires.

With a policy-based design, we could decide on a per-pointer basis,
perhaps with type-wide or program-wide defaults. And this is an
example of a policy which /ought/ to be part of the pointer's type, so
that passing a multi-threaded pointer to single-threaded code becomes a
compile-time error.

-- Dave Harris, Nottingham, UK

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 14 Jul 2003 21:56:49 +0000 (UTC)
Raw View
pdimov@mmltd.net (Peter Dimov) wrote (abridged):
> [sometimes] the optimization you want is not possible.

OK. I tend to think of reference-counting as poor-man's garbage
collection, and I don't care if a counted object is destroyed early
if there are no reachable references to it, but I suppose C++ isn't
like that. Would a special dispensation to optimise temporary
share_ptrs be worth considering?

There remain some important special cases, for example when all the
code is inline and the compile can see that reset() is not called.

    typedef boost::shared_ptr<int> IntPtr;

    void func1( IntPtr p );

    inline void func2( IntPtr p ) {
        func1( p );
    }

    void func3() {
         func2( IntPtr( new int ) );
    }

The compiler has to increment and decrement when calling func1() for
the reason you explained, but that reason does not apply to the call
of func2(). If IntPtr were typedef'd as (int *), func2() could be a
zero-cost abstraction. It's a shame to lose that because of
use_count(), don't you think?

If use_count() must be kept, then the original poster is right and
the programmer must indicate explicitly when the optimisation is to
be used. This becomes a specific example of the general "pilfer",
"destructive copy" or "move" problem. It's probably better to adopt
a general solution, such as the "&&" reference-to-temporary
proposal, rather than address it specifically in the smart pointer
classes.

-- Dave Harris, Nottingham, UK

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: terekhov@web.de (Alexander Terekhov)
Date: Mon, 14 Jul 2003 21:59:22 +0000 (UTC)
Raw View
Richard Smith wrote:
[...]
>   http://www.terekhov.de/pthread_refcount_t/poor-man/beta2/prefcnt.c
>
> I notice you use a mutex to ensure syncronisation in all
> the atomic functions, so we're back to square one -- all
> we've succeeded in doing is moving the mutex from the
> smart pointer library to the pthread_refcount library.

That's why I called that stuff "poor-man". ;-) A *blocking*
pthread_refcount_t is pretty much useless and was only added
to "promote portability". Non-blocking stuff is what you want
to have/use in any "serious appl". See my "experimental take"
on it... and it's written in C++, BTW. ;-)

>
> Atomic operations such as (++*rc) and (--*rc == 0) can often
> be implemented using the atomic operations of the underlying
> architecture.  For example, Intel's x86 series of processors
> [where x >= 4] allow the use of the LOCK prefix to
> instructions such as INC and DEC, thus using hand-crafted
> machine code, it is possible to write very efficient atomic
> increment / decrement functions.  Windows supplies
> Interlocked{Increment, Decrement} functions that do
> precisely this.

Don't get me started on silly MS-interlocked stuff, please.

>
> Unfortunately, I'm not aware of any similar way to implement
> your pthread_refcount_increment_positive function.

http://google.com/groups?selm=3EC108B8.980ACE53%40web.de
(Subject: Re: scoped static singleton question)

<quote>

int pthread_refcount_increment_positive(
      pthread_refcount_t * refcount
    )
{
  std::size_t val;
  do {
    val = refcount->atomic.load(); // Naked
    if (!val) return PTHREAD_REFCOUNT_DROPPED_TO_ZERO;
    assert(PTHREAD_REFCOUNT_MAX > val);
  } while (!refcount->atomic.attempt_update(val, val+1)); // Naked
 return 0;
}

</quote>

-ALSO-

http://www.terekhov.de/pthread_refcount_t/experimental/refcount.cpp

<quote>

  bool increment_if_not_min() throw() {
    numeric val;
    do {
      val = m_value.load(msync::none);
      assert(max() > val);
      if (min() == val)
        return false;
    } while (!m_value.attempt_update(val, val + 1, msync::none));
    return true;
  }

</quote>

>                                                    This is
> the problem, and means that the TR1 smart pointer library
> violates the zero-overhead principle:  in threaded code, you
> cannot use shared_ptr without paying a overhead for weak_ptr
> support, even when you do not use weak_ptr.  How large that
> overhead is, and whether it's a price worth paying is
> another question.

The only overhead is extra space needed for counter and one
extra decrement [initialization aside for a moment] in:

http://groups.google.com/groups?selm=3EC0F1F1.B78AA0DA%40web.de
(Subject: Re: Atomic ops on Intel: do they sync?)

<quote>

  void release_strong() {
    int status = pthread_refcount_decrement(&strong_count);
    if (PTHREAD_REFCOUNT_DROPPED_TO_ZERO == status) {
      destruct_object();
      status = pthread_refcount_decrement_rel(&weak_count);
      if (PTHREAD_REFCOUNT_DROPPED_TO_ZERO == status)
        destruct_self();
    }
  }

</quote>

That's it.

regards,
alexander.

--
http://lists.boost.org/MailArchives/boost/msg43729.php
(Subject: [boost] Re: Weak ref. via atomicity (RE: Smart pointers:...))

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Tue, 15 Jul 2003 03:01:53 +0000 (UTC)
Raw View
David Abrahams wrote:

> Well, I think it's easy to get zero-overhead shared_ptr copying back.
> Right now we have "cheap" weak_ptr copies and "expensive" shared_ptr
> copies.  Shouldn't the balance be pushed in the other direction?

Ideally, yes.

> I might be missing something, but it seems to me that if, instead of
> storing use_count, you store the difference between weak_count and
> use_count, shared_ptr can be copied with an atomic increment operation
> on the weak_count.

I think the shared_ptr<T>::shared_ptr( const weak_ptr<T>& )
constructor still causes problems.  Semantically, it needs
to do the following:

  if ( weak_count == difference ) throw bad_weak_ptr;
  ++weak_count;

Imagine at the start of this block of code, there is one
weak_ptr and one shared_ptr.  What happens if between these
two lines, another thread destroys the final shared_ptr,
decrementing weak_count to be equal to difference?  This
means (I think) we need a mutex to protect this block of
code.  And as soon as we have a mutex here, we need one
where-ever we manipulate weak_count, including the
constructors and destructor of shared_ptr.

Or perhaps I'm misunderstanding what you intended?

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Tue, 15 Jul 2003 03:02:53 +0000 (UTC)
Raw View
In article <memo.20030713134546.56263B@brangdon.madasafish.com>, Dave
Harris <brangdon@cix.co.uk> wrote:

| Would a special dispensation to optimise temporary
| share_ptrs be worth considering?

Sounds like another excellent motivation for move semantics! :-)

--
Howard Hinnant
Metrowerks

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Tue, 15 Jul 2003 04:03:22 +0000 (UTC)
Raw View
brangdon@cix.co.uk (Dave Harris) writes:

> dave@boost-consulting.com (David Abrahams) wrote (abridged):
>> That's an interesting point.  Of course, COM's apartment model is
>> quite problematic in many cases, and having to decide
>> single/multi-threadedness per-type instead of per-object can be a
>> problem too, can't it?
>
> Deciding per-type is less of a problem than deciding per-program, as the
> current shared_ptr<> proposal requires.
>
> With a policy-based design, we could decide on a per-pointer basis,
> perhaps with type-wide or program-wide defaults.

You don't need policies for that.  Policies decide per-type.  To
decide per-pointer, you need a flag and runtime checks.

> And this is an example of a policy which /ought/ to be part of the
> pointer's type, so that passing a multi-threaded pointer to
> single-threaded code becomes a compile-time error.

Why should it be one?  Most single-threaded code is perfectly
threadsafe.  It's usually only when you start doing "threading things"
that you get in trouble.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Tue, 15 Jul 2003 15:35:50 +0000 (UTC)
Raw View
Peter Dimov wrote:

> int pthread_refcount_increment_positive( long * rc )
> {
>     long tmp;
>
>     do
>     {
>         tmp = *rc;
>         if(tmp == 0) return PTHREAD_REFCOUNT_DROPPED_TO_ZERO;
>     }
>     while( InterlockedCompareExchange(rc, tmp + 1, tmp) != tmp );
>
>     return 0;
> }

Thank you.

I had been worried that a combination of the need to
support weak_ptr and threads would give an unnecessary
overhead to simple uses of shared_ptr.  I'm now happy that
this is not the case.

I don't know how many platform provide something like
InterlockedCompareExchange, but I know all recent ix86s
processors allow this (via the lock cmpxchg instruction).

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Tue, 15 Jul 2003 15:35:51 +0000 (UTC)
Raw View
richard@ex-parrot.com (Richard Smith) writes:

>> I might be missing something, but it seems to me that if, instead of
>> storing use_count, you store the difference between weak_count and
>> use_count, shared_ptr can be copied with an atomic increment operation
>> on the weak_count.
>
> I think the shared_ptr<T>::shared_ptr( const weak_ptr<T>& )
> constructor still causes problems.  Semantically, it needs
> to do the following:
>
>   if ( weak_count == difference ) throw bad_weak_ptr;
>   ++weak_count;
>
> Imagine at the start of this block of code, there is one
> weak_ptr and one shared_ptr.  What happens if between these
> two lines, another thread destroys the final shared_ptr,
> decrementing weak_count to be equal to difference?  This
> means (I think) we need a mutex to protect this block of
> code.  And as soon as we have a mutex here, we need one
> where-ever we manipulate weak_count, including the
> constructors and destructor of shared_ptr.
>
> Or perhaps I'm misunderstanding what you intended?

Nope, you understood what I meant.  At the risk of underestimating a
threading problem, I think it might still be solvable.

First, to clarify, let's rename some variables.  In the current
design weak_count is really (#weak + #shared), so the difference
we're talking about is really (#weak + #shared) - #shared = #weak.

So, I'm renaming

    "weak_count" to "total"
 and
    "difference" to "nweak"

The goal is to use a mutex only for weak_ptr operations, and use
atomic operations on shared_ptr. Since only weak_ptr operations need
to manipulate nweak, we hope to protect manipulations of nweak with a
mutex but use atomic operations to manipulate total.

Invariants:

     total >= nweak

     once the last shared_ptr to the pointee disappears, there can
     never be another.

OK, back to shared_ptr<T>::shared_ptr( const weak_ptr<T>& )

Since this function manipulates a weak_ptr<T>, it is protected by a
mutex and no other weak_ptr operations on the same pointee can happen
simultaneously, and nweak will not change during this function.

Pure shared_ptr operations can happen at will, so total will be
"volatile" during the execution of this function, but it will never be
less than nweak.  Because no shared_ptrs can be created once the last
one has disappeared, however, a special case holds that once there are
no shared_ptrs, total becomes non-"volatile" when the mutex is held.

If we *start* with ++total, we can prevent any concurrent shared_ptr
destructions from deleting the pointee (if any).  Then, assuming the
increment also allows us to read total, we can check whether total ==
nweak + 1 and if so, we know that there are no shared_ptrs left.  We
can then decrement total and throw.  Otherwise we can proceed.

Any problem here?
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: kanze@gabi-soft.fr
Date: Tue, 15 Jul 2003 23:50:32 +0000 (UTC)
Raw View
petebecker@acm.org (Pete Becker) wrote in message
news:<3F106B87.C9AD7DFD@acm.org>...
> Howard Hinnant wrote:

> > This is drifting from the subject of the original post somewhat, but
> > I have been questioning the value of a "MT-safe" shared_ptr.  I
> > recently used shared_ptr in an MT environment as part of the
> > implementation of a larger object.  In this particular application I
> > found it better to put the synchronization primitive at a higher
> > level than shared_ptr's counts.  And once placed at that higher
> > level, an additional mutex (or whatever) for shared_ptr's count was
> > just an expensive redundancy.

> In another context, locking inside a stream inserter or extractor
> doesn't help multi-threaded code, because it doesn't guarantee that
> multiple insertions will operate atomically. So the application has to
> add locks around all blocks io operations to the same stream, and the
> locks in the inserters and extractors are then redundant.

> Thread safety is a design issue. Locks (including locks in smart
> pointers and stream operations) are tools. Putting them inside a
> library is usually the wrong thing -- too low level. They protect the
> library from corruption, but that's not really a problem that ought to
> be solved, because the application still won't be correct. A correctly
> written multi-threaded application typically won't need those low
> level locks.

It depends.  When two separate instances transparently share data, you
can't leave locking up to the user (who is not supposed to know about
the shared data).  This is a problem with COW strings, for example.

With regards to reference counted smart pointers, I don't know, but my
feeling would be that they also need some locking.  Globally, I expect
the same degree of thread safety that Posix gives me for built-in
objects.  And I can certainly do something like:

    MyClass* p ;
    MyClass* q ;
    p = q ;

within a single thread, without locking, regardless of what other
pointers in other threads might happen to point to the same object as
q.  I would rather expect a similar guarantee from any smart pointer.

--
James Kanze           GABI Software        mailto:kanze@gabi-soft.fr
Conseils en informatique orient   e objet/     http://www.gabi-soft.fr
                    Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: rmaddox@isicns.com (Randy Maddox)
Date: Tue, 15 Jul 2003 23:50:42 +0000 (UTC)
Raw View
petebecker@acm.org (Pete Becker) wrote in message news:<3F106B87.C9AD7DFD@acm.org>...
>
> In another context, locking inside a stream inserter or extractor
> doesn't help multi-threaded code, because it doesn't guarantee that
> multiple insertions will operate atomically. So the application has to
> add locks around all blocks io operations to the same stream, and the
> locks in the inserters and extractors are then redundant.

Absolutely correct.  In this particular case the correct solution is
to provide an auxiliary StreamLock class that, on insertion
to/extraction from a stream, creates a temporary object that exists
for the duration of the full i/o expression.  This has been discussed
elsewhere on this ng and has been used successfully.  The other
benefit of this approach is that single-threaded code does not need to
use the StreamLock and therefore pays no price for what it does not
use.

>
> Thread safety is a design issue. Locks (including locks in smart
> pointers and stream operations) are tools. Putting them inside a library
> is usually the wrong thing -- too low level. They protect the library
> from corruption, but that's not really a problem that ought to be
> solved, because the application still won't be correct. A correctly
> written multi-threaded application typically won't need those low level
> locks.
>

Absolutely correct again.  A thread-safe library can protect only its
own internal data structures, which is necessary but not sufficient
for the user of that library.  And once such a user has added locking
at the appropriate level, the internal locking in the library is
generally, but not always, redundant so we sometimes end up paying a
cost for no benefit.

In general, a correctly designed multi-threaded program must provide
for its own locking and synchronization at a level higher than any
library.  Thus I believe that while C++ should provide portable
abstractions that enable a multi-threaded program to be designed
correctly, it is not generally appropriate to build thread-safety into
most libraries since that is the wrong level of abstraction for this.
Some simple data structures with limited interfaces, such as a
reference-counted smart pointer or a stack or queue, can provide
appropriate thread-safety, but most others simply cannot, and should
not try to do so.

Randy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Wed, 16 Jul 2003 02:02:26 +0000 (UTC)
Raw View
Howard Hinnant wrote:
> In article <memo.20030713134546.56263B@brangdon.madasafish.com>, Dave
> Harris <brangdon@cix.co.uk> wrote:
>
> | Would a special dispensation to optimise temporary
> | share_ptrs be worth considering?
>
> Sounds like another excellent motivation for move semantics! :-)
>

Please fill in the dots.  Are you advocating somthing like the
AT_LifeView but supported by the compiler ?

G

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Wed, 16 Jul 2003 02:02:47 +0000 (UTC)
Raw View
brangdon@cix.co.uk (Dave Harris) wrote in message news:<memo.20030713134546.56263B@brangdon.madasafish.com>...
> pdimov@mmltd.net (Peter Dimov) wrote (abridged):
> > [sometimes] the optimization you want is not possible.
>
> OK. I tend to think of reference-counting as poor-man's garbage
> collection, and I don't care if a counted object is destroyed early
> if there are no reachable references to it, but I suppose C++ isn't
> like that. Would a special dispensation to optimise temporary
> share_ptrs be worth considering?

I think that "as if" and 12.2 are all that is needed. shared_ptr
instances are like all other C++ objects, and the rules are the same.

> There remain some important special cases, for example when all the
> code is inline and the compile can see that reset() is not called.
>
>     typedef boost::shared_ptr<int> IntPtr;
>
>     void func1( IntPtr p );
>
>     inline void func2( IntPtr p ) {
>         func1( p );
>     }
>
>     void func3() {
>          func2( IntPtr( new int ) );
>     }
>
> The compiler has to increment and decrement when calling func1() for
> the reason you explained, but that reason does not apply to the call
> of func2(). If IntPtr were typedef'd as (int *), func2() could be a
> zero-cost abstraction. It's a shame to lose that because of
> use_count(), don't you think?

No, I don't think. If the compiler can see that there is no reset(),
the compiler can see that there is no use_count(). Even if there _is_
an use_count() call, the compiler can replace it with the proper
value, if it's known from whole program analysis. The spec says that
use_count() returns the number of shared_ptr instances sharing
ownership, not the value of the reference count. There is no reference
count as far as the spec is concerned. :-)

If the circumstances allow the compiler to undetectably alter the
number of instances because of "as if", it can also alter the value of
use_count() to account for the difference.

By the way, the temporary in your example can always be optimized away
because of 12.2.

> If use_count() must be kept, then the original poster is right and
> the programmer must indicate explicitly when the optimisation is to
> be used. This becomes a specific example of the general "pilfer",
> "destructive copy" or "move" problem. It's probably better to adopt
> a general solution, such as the "&&" reference-to-temporary
> proposal, rather than address it specifically in the smart pointer
> classes.

Move doesn't help shared_ptr much since the move cost is fairly close
to the copy cost.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Wed, 16 Jul 2003 02:03:07 +0000 (UTC)
Raw View
Carl Daniel wrote:

> I was thrown aback by the use of the name "mutex", when in fact it doesn't
> map to an OS mutex at all, but a home-grown spin-lock or CriticalSection,
> depending on some #defines and the platform.

I'm not sure this is particularly relevant.  Doing some
measurments using the second test program from my earlier
post, I get the following figures:

  0.221 multi-threaded using a pthread_mutex
  0.240 multi-threaded using a spinlock
  0.077 single-threaded

i.e. that a pthread_mutex is faster than a hand-coded
spinlock.  Anyhow, this is drifting off topic.  The point is
that using some 'heavy-weight' mechanism to guarantee
atomicity (whether a mutex or a spinlock) imposes an
noticeable overhead when compared to the single-threaded
version.

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Wed, 16 Jul 2003 02:03:44 +0000 (UTC)
Raw View
petebecker@acm.org (Pete Becker) wrote in message news:<3F106B87.C9AD7DFD@acm.org>...
> Howard Hinnant wrote:
> >
> > This is drifting from the subject of the original post somewhat, but I
> > have been questioning the value of a "MT-safe" shared_ptr.  I recently
> > used shared_ptr in an MT environment as part of the implementation of a
> > larger object.  In this particular application I found it better to put
> > the synchronization primitive at a higher level than shared_ptr's
> > counts.  And once placed at that higher level, an additional mutex (or
> > whatever) for shared_ptr's count was just an expensive redundancy.
> >
>
> In another context, locking inside a stream inserter or extractor
> doesn't help multi-threaded code, because it doesn't guarantee that
> multiple insertions will operate atomically. So the application has to
> add locks around all blocks io operations to the same stream, and the
> locks in the inserters and extractors are then redundant.
>
> Thread safety is a design issue. Locks (including locks in smart
> pointers and stream operations) are tools. Putting them inside a library
> is usually the wrong thing -- too low level. They protect the library
> from corruption, but that's not really a problem that ought to be
> solved, because the application still won't be correct. A correctly
> written multi-threaded application typically won't need those low level
> locks.

shared_ptr "locks" are different. They are required to allow user code
to manipulate two different shared_ptr instances simultaneously
without corruption, i.e. to achieve the "as thread safe as an int"
level ("basic" thread safety).

Concurrent read+write, write+write access to a single shared_ptr
("strong" thread safety) still requires user space lock.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Wed, 16 Jul 2003 02:04:40 +0000 (UTC)
Raw View
cpdaniel@nospam.mvps.org ("Carl Daniel") wrote in message news:<VKqQa.2731$A37.2516@newssvr16.news.prodigy.com>...
> My re-running of your test confirms about a 7x performance cost.

The totally nonportable

  http://www.pdimov.com/cpp/shared_count_x86_exp.hpp

performs even better. Now "all" we need is a formal proof that the
atomic updates work. :-)

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Wed, 16 Jul 2003 16:47:39 +0000 (UTC)
Raw View
In article <bf0ivi$cq6@dispatch.concentric.net>, Gianni Mariani
<gi2nospam@mariani.ws> wrote:

| Howard Hinnant wrote:
| > In article <memo.20030713134546.56263B@brangdon.madasafish.com>, Dave
| > Harris <brangdon@cix.co.uk> wrote:
| >
| > | Would a special dispensation to optimise temporary
| > | share_ptrs be worth considering?
| >
| > Sounds like another excellent motivation for move semantics! :-)
| >
|
| Please fill in the dots.  Are you advocating somthing like the
| AT_LifeView but supported by the compiler ?

No, I'm saying that shared_ptr could benefit from:

http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm

with something like (untested):

template <class T>
inline
shared_ptr<T>::shared_ptr(shared_ptr&& r)
   : ptr_(r.ptr_),
     s_(r.s_)
{
   r.ptr_ = 0;
   r.s_ = 0;
}

template <class T>
inline
shared_ptr<T>::shared_ptr(const shared_ptr& r)
   : ptr_(r.ptr_),
     s_(r.s_)
{
   if (s_)
      s_->attach();
}

All attach() has to do is increment the use counts (and it is not a
virtual call).  But if the shared_ptr is protecting the use counts with
a thread synchronization device, then constructing from a temporary can
avoid that.

Even if there is no MT code, I believe the move constructor will still
be significantly faster than the copy constructor.  Both of course are
very fast, but just roughly counting instructions, it looks to me like
the move constructor might be as much as twice as fast.  In a heavily
used utility, you don't want to throw something like that away.

--
Howard Hinnant
Metrowerks

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: markus.mauhart@nospamm.chello.at ("Markus Mauhart")
Date: Wed, 16 Jul 2003 16:47:51 +0000 (UTC)
Raw View
"David Abrahams" <dave@boost-consulting.com> wrote ...
>
> First, to clarify, let's rename some variables.  In the current
> design weak_count is really (#weak + #shared), so the difference
> we're talking about is really (#weak + #shared) - #shared = #weak.
>
> So, I'm renaming
>
>     "weak_count" to "total"
>  and
>     "difference" to "nweak"
>
> The goal is to use a mutex only for weak_ptr operations, and use
> atomic operations on shared_ptr. Since only weak_ptr operations need
> to manipulate nweak, we hope to protect manipulations of nweak with a
> mutex but use atomic operations to manipulate total.
>
> Invariants:
>
>      total >= nweak
>
>      once the last shared_ptr to the pointee disappears, there can
>      never be another.
>
> OK, back to shared_ptr<T>::shared_ptr( const weak_ptr<T>& )
>
> Since this function manipulates a weak_ptr<T>, it is protected by a
> mutex and no other weak_ptr operations on the same pointee can happen
> simultaneously, and nweak will not change during this function.
>
> Pure shared_ptr operations can happen at will, so total will be
> "volatile" during the execution of this function, but it will never be
> less than nweak.  Because no shared_ptrs can be created once the last
> one has disappeared, however, a special case holds that once there are
> no shared_ptrs, total becomes non-"volatile" when the mutex is held.
>
> If we *start* with ++total, we can prevent any concurrent shared_ptr
> destructions from deleting the pointee (if any).  Then, assuming the
> increment also allows us to read total, we can check whether total ==
> nweak + 1 and if so, we know that there are no shared_ptrs left.  We
> can then decrement total and throw.  Otherwise we can proceed.
>
> Any problem here?

IMO yes, what happens during shared's and weak's dtor ?
In your model ...

shared::~shared
{
int const shared = InterlockedDecrement (&ref_counts->total);
if (shared == 0) delete pointer;
problem
}
Now you would also have to check ref_counts->nweak ... cause
the last one has to delete also ref_counts.
But you may not access ref_counts anymore, cause in your model, during
creation of the shared_ptr, you only incremented total, hence after
decrementing it outside of a critical section, you have lost any right
to access refcounts. E.g. immediately after your "--total", inside
another thread, the last weak_ptr could have died, saw total==0 and
nweak==0 and therefore deleted the complete ref_counts object.

But there might be a cheap poor man's solution using ...

  struct RC {
    union {UINT16 ui16[2] ;UINT32 ui32 ;};
    UINT16& shared_count_ ()  {return ui16[0] ;} // num shared_ptrs
    UINT16& weak_count_   ()  {return ui16[1] ;} // num weak_ptrs + shared_ptrs
  };

.... and "LONG InterlockedExchangeAdd (LONG* pvalue ,LONG difference)",
if 2^16 is enough for you, and if InterlockedExchangeAdd() is portable
enough.


Regards,
Markus.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Wed, 16 Jul 2003 20:11:07 +0000 (UTC)
Raw View
hinnant@metrowerks.com (Howard Hinnant) wrote (abridged):
> Even if there is no MT code, I believe the move constructor will still
> be significantly faster than the copy constructor.  Both of course are
> very fast, but just roughly counting instructions, it looks to me like
> the move constructor might be as much as twice as fast.

There may be further gains from the destructor. Once all the code is
inlined, the compiler may be able to tell from the assignments in the move
constructor that the destructor will do nothing. And then it may be able
to remove the 0 assignments in the move constructor, too. (Obviously this
depends on details of the implementation.)

So the total cost of a move may be just 2 assignments.

-- Dave Harris, Nottingham, UK

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: petebecker@acm.org (Pete Becker)
Date: Wed, 16 Jul 2003 21:27:46 +0000 (UTC)
Raw View
kanze@gabi-soft.fr wrote:
>
> > Thread safety is a design issue. Locks (including locks in smart
> > pointers and stream operations) are tools. Putting them inside a
> > library is usually the wrong thing -- too low level. They protect the
> > library from corruption, but that's not really a problem that ought to
> > be solved, because the application still won't be correct. A correctly
> > written multi-threaded application typically won't need those low
> > level locks.
>
> It depends.

That's why I said "usually the wrong thing," and "typically won't need
those low level locks." I probably shouldn't have included smart
pointers here, since they seem to be distracting everyone from the point
I was making. Putting locks in smart pointers doesn't make user code
thread safe.

--

"To delight in war is a merit in the soldier,
a dangerous quality in the captain, and a
positive crime in the statesman."
 George Santayana

"Bring them on."
 George W. Bush

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Wed, 16 Jul 2003 21:27:51 +0000 (UTC)
Raw View
Gianni Mariani wrote:

> Howard Hinnant wrote:
> > Sounds like another excellent motivation for move semantics! :-)
>
> Please fill in the dots.  Are you advocating somthing like the
> AT_LifeView but supported by the compiler ?

It is described in paper N1377:

  http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Thu, 17 Jul 2003 01:44:09 +0000 (UTC)
Raw View
Discussing:

   typedef boost::shared_ptr<int> IntPtr;

   void func1( IntPtr p );

   inline void func2( IntPtr p ) {
       func1( p );
   }

   void func3() {
        IntPtr p( new int ) );
        func2( p );
   }

pdimov@mmltd.net (Peter Dimov) wrote (abridged):
> > The compiler has to increment and decrement when calling func1() for
> > the reason you explained, but that reason does not apply to the call
> > of func2(). If IntPtr were typedef'd as (int *), func2() could be a
> > zero-cost abstraction. It's a shame to lose that because of
> > use_count(), don't you think?
>
> No, I don't think. If the compiler can see that there is no reset(),
> the compiler can see that there is no use_count().

There is no reset() or use_count() in func2(), but there might be either
in func1(). Inside func1() the count will be > 1, so reset() will not
cause the int to be deleted, so the only way to detect the count (that I
can see) is to use use_count() (or unique()).


> Even if there _is_ an use_count() call, the compiler can replace it
> with the proper value, if it's known from whole program analysis.

I'd rather a system which could be optimised without whole program
analysis. Especially as there could be other calls to func1() with
different use counts.


> If the circumstances allow the compiler to undetectably alter the
> number of instances because of "as if", it can also alter the value of
> use_count() to account for the difference.

Interesting point, but I don't think it can do that here. The compiler has
to behave as if func2() were not inlined.


> By the way, the temporary in your example can always be optimized away
> because of 12.2.

Thanks - that was a mistake. Corrected in the above code.


> Move doesn't help shared_ptr much since the move cost is fairly close
> to the copy cost.

These costs are being debated elsewhere. The move cost could be 2
assignments, with no need for any kind of threading protection, and no
need to access the pointed-to memory.

But I agree it could be an interesting case study for advocates of move
semantics :-)

-- Dave Harris, Nottingham, UK

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: emild@collectivestudios.com (Emil Dotchevski)
Date: Thu, 17 Jul 2003 16:38:08 +0000 (UTC)
Raw View
>     {
>         boost::shared_ptr<int> p( new int );
>         func( p );
>     }

The presence of the use_count() *function*, I think, does not in any
way interfere with optimizations of temporaries. Correct me if I am
wrong, but it seems to me use_count() does not imply the use of
external counter, therefore there is no requirement for the
implementation to increment anything when you make a copy of a
shared_ptr.

--Emil

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: do-not-spam-benh@bwsint.com (Ben Hutchings)
Date: Thu, 17 Jul 2003 16:38:22 +0000 (UTC)
Raw View
In article <uadbfdiqq.fsf@boost-consulting.com>, David Abrahams wrote:
> richard@ex-parrot.com (Richard Smith) writes:
<snip>
>> I think the shared_ptr<T>::shared_ptr( const weak_ptr<T>& )
>> constructor still causes problems.  Semantically, it needs
>> to do the following:
>>
>>   if ( weak_count == difference ) throw bad_weak_ptr;
>>   ++weak_count;
>>
>> Imagine at the start of this block of code, there is one
>> weak_ptr and one shared_ptr.  What happens if between these
>> two lines, another thread destroys the final shared_ptr,
>> decrementing weak_count to be equal to difference?
<snip>

[Rename weak_count to total and difference to nweak]

> Pure shared_ptr operations can happen at will, so total will be
> "volatile" during the execution of this function, but it will never be
> less than nweak.  Because no shared_ptrs can be created once the last
> one has disappeared, however, a special case holds that once there are
> no shared_ptrs, total becomes non-"volatile" when the mutex is held.
>
> If we *start* with ++total, we can prevent any concurrent shared_ptr
> destructions from deleting the pointee (if any).  Then, assuming the
> increment also allows us to read total, we can check whether total ==
> nweak + 1 and if so, we know that there are no shared_ptrs left.  We
> can then decrement total and throw.  Otherwise we can proceed.
>
> Any problem here?

Yes - the pointee will be leaked if this constructor races with the
destructor.

It's not important to keep a counter of weak pointers.  The transitions
we care about are:

1. Number of strong pointers drops to zero.
2. Number of strong or weak pointers drops to zero.

Alexander Terekhov presented a solution which reliably detects these
transitions without the need to update two counters.  He used a count
of strong pointers and a count of weak pointers + (strong count != 0).
I shall attempt to translate this to fit into the Boost implementation,
using the following platform-dependent functions:

    // Atomically read var
    long atomic_read(const volatile long & var);
    // Atomically set var = new_val iff var == old_val.  Return success
    // indication.
    bool atomic_update(volatile long & var, long old_val, long new_val);

Here's my attempt:

class sp_counted_base
{
public:

    sp_counted_base(): use_count_(1), self_count_(1)
    {
    }

    virtual ~sp_counted_base() // nothrow
    {
    }

    // dispose() is called when use_count_ drops to zero, to release
    // the resources managed by *this.

    virtual void dispose() = 0; // nothrow

    // destruct() is called when self_count_ drops to zero.

    virtual void destruct() // nothrow
    {
        delete this;
    }

    virtual void * get_deleter(std::type_info const & ti) = 0;

    void add_ref()
    {
        long old_use_count;
        do
        {
            old_use_count = atomic_read(use_count_);
            if (old_use_count == 0)
                throw bad_weak_ptr();
        }
        while (!atomic_update(use_count_, old_use_count,
                              old_use_count + 1));
    }

    void release() // nothrow
    {
        long old_use_count;
        do
            old_use_count = atomic_read(use_count_);
        while (!atomic_update(use_count_, old_use_count,
                              old_use_count - 1));

        if (old_use_count == 1)
        {
            dispose();
            weak_release();
        }
    }

    void weak_add_ref() // nothrow
    {
        long old_self_count;
        do
            old_self_count = atomic_read(self_count_);
        while (!atomic_update(self_count_, old_self_count,
                              old_self_count + 1));
    }

    void weak_release() // nothrow
    {
        long old_self_count;
        do
            old_self_count = atomic_read(self_count_);
        while (!atomic_update(self_count_, old_self_count,
                              old_self_count - 1));

        if (old_self_count == 1)
            destruct();
    }

    long use_count() const // nothrow
    {
        return atomic_read(use_count_);
    }

private:

    sp_counted_base(sp_counted_base const &);
    sp_counted_base & operator= (sp_counted_base const &);

    // inv: self_count_ != 0

    long use_count_;
    long self_count_;
};

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Fri, 18 Jul 2003 00:49:03 +0000 (UTC)
Raw View
do-not-spam-benh@bwsint.com (Ben Hutchings) wrote in message news:<slrnbhbnpc.154.do-not-spam-benh@tin.bwsint.com>...
>
> Alexander Terekhov presented a solution which reliably detects these
> transitions without the need to update two counters.  He used a count
> of strong pointers and a count of weak pointers + (strong count != 0).
> I shall attempt to translate this to fit into the Boost implementation,

[...]

>     sp_counted_base(): use_count_(1), self_count_(1)
>     {
>     }

I see it now; thank you very much for extracting the solution from one
of Alexander's links in a form that I can easily understand! :-)

This approach is about 20-25% faster than the "two count atomic" case,
and about 20-25% slower than the single threaded case.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Fri, 18 Jul 2003 02:37:30 +0000 (UTC)
Raw View
brangdon@cix.co.uk (Dave Harris) wrote in message news:<memo.20030716202424.53969A@brangdon.madasafish.com>...
> Discussing:
>
>    typedef boost::shared_ptr<int> IntPtr;
>
>    void func1( IntPtr p );
>
>    inline void func2( IntPtr p ) {
>        func1( p );
>    }
>
>    void func3() {
>         IntPtr p( new int ) );
>         func2( p );
>    }
>
> pdimov@mmltd.net (Peter Dimov) wrote (abridged):

[...]

> > By the way, the temporary in your example can always be optimized away
> > because of 12.2.
>
> Thanks - that was a mistake. Corrected in the above code.

I agree that in your corrected example, a compiler that has an
intrinsic shared_ptr type and understands that one instance is a
special case (from "observable behavior" point of view) can completely
optimize out func2::p, since without use_count() the difference
between two and three instances is not detectable.

With use_count(), assuming a typical reference counted implementation,
the compiler must increment __use_count by 2 and not one. There will
be no performance difference. The storage for func2::p can still be
optimized out since nobody can see it, and I think that this can be
performed by an "ordinary" compiler with no extra knowledge of
shared_ptr (the copy constructor and destructor must be visible, of
course).

But I'm sure you understand that this is an academic example. :-) A
hypothetical compiler that has intrinsic std:: types (we are still
waiting for it) will probably start with std::vector - much, much
better ROI.

All real world implementations will likely have use_count() since they
need to be testable; if we don't include it in the specification it
will probably still be there but will be named __use_count(). A public
use_count() query is useful when testing or debugging code.

> > Move doesn't help shared_ptr much since the move cost is fairly close
> > to the copy cost.
>
> These costs are being debated elsewhere. The move cost could be 2
> assignments, with no need for any kind of threading protection, and no
> need to access the pointed-to memory.

True, moving a shared_ptr is - of course - faster than copying one,
but there are much better motivating examples. O(1) with a smaller
constant doesn't even come close to O(1) without an allocation instead
of O(N) with allocation.

There is one additional subtlety in the threading case BTW, copying a
shared_ptr is a read access while moving one is a write access, so
when moving an lvalue one needs to ensure that other threads can't see
it.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Fri, 18 Jul 2003 02:38:58 +0000 (UTC)
Raw View
dave@boost-consulting.com (David Abrahams) wrote (abridged):
> >> That's an interesting point.  Of course, COM's apartment model is
> >> quite problematic in many cases, and having to decide
> >> single/multi-threadedness per-type instead of per-object can be a
> >> problem too, can't it?
> >
> > With a policy-based design, we could decide on a per-pointer basis,
> > perhaps with type-wide or program-wide defaults.
>
> You don't need policies for that.  Policies decide per-type.

When we say "per-object", I take it to mean the pointed-to object, not the
pointer itself. And similarly, "per-type" means the type of the pointed-to
object. A policy-based pointer design would decide per the type of the
pointer, which is different. We could have two pointers with different
policies pointing to the same object.

    Ptr<Object,singlethreaded> p1( new Object );
    Ptr<Object,multithreaded> p2 = p1;


> To decide per-pointer, you need a flag and runtime checks.

OK, but that wasn't what I meant.


> > And this is an example of a policy which /ought/ to be part of the
> > pointer's type, so that passing a multi-threaded pointer to
> > single-threaded code becomes a compile-time error.
>
> Why should it be one?  Most single-threaded code is perfectly
> threadsafe.

Not if it is running concurrently with other single-threaded code that
shares access to the same object. It would be reasonable to have an
explicit conversion (eg for when a higher level lock keeps the other
threads out), but not an implicit conversion. An assignment such as the
above ought to be an error.

("Single-threaded code" here means code using single-threaded idioms,
including single-threaded pointers. The compiler won't know whether there
are actually other active threads when such code is run.)


> It's usually only when you start doing "threading things" that you
> get in trouble.

If you are not doing threading things, you don't need multi-threaded
pointers and the conversion issue doesn't arise.

-- Dave Harris, Nottingham, UK

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: cpdaniel@nospam.mvps.org ("Carl Daniel")
Date: Fri, 11 Jul 2003 01:38:48 +0000 (UTC)
Raw View
"Richard Smith" wrote:

> If you mean that IUnknown::AddRef and IUnknown::Release are virtual
> and can't be inlined, then you're quite correct.  Boost's reference
> counting mechanism does not use this -- it has a it's own independent
> reference count.  Only when Boost's reference count drops to zero
> does it (virtually) call the custom deletion function, which will
> call IUnknown::Release.  Thus you can copy boost::shared_ptr's around
> as much as you want, and you will not incur the overhead of calls to
> IUnknown::Release until the object is finally destroyed.  The only
> overhead is from Boost's own reference counting mechanism, which is
> much more efficient.(And with a bit of hand-crafted assembler to do
> the atomic incrementing, etc. the mutex in the 1.30 implementation
> could be removed, making it even more so.  Or perhaps I'm missing
> something?)

Perhaps I'm missing something obvious, but the use of a mutex to provide MT
safety in boost::shared_ptr guarantees that it is in fact far less efficient
than typical COM reference counting, including the virtual function call
overhead.  If shared_ptr was updated to use InterlockedIncrement, then it
could be faster than the typical COM AddRef() call due to the virtual
function call in the latter.

Worse, COM objects "know" how their reference counts should be maintained.
A COM object that's "single threaded" can be used in an MT program, and yet
never use synchronization on access to the reference count (since it's an
error for multiple threads to ever enter the object).  Thus, for a very
common case, the boost::shared_ptr mechanism is nearly guaranteed to be less
efficient than normal COM reference counting.

-cd

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com (Richard Smith)
Date: Fri, 11 Jul 2003 17:52:44 +0000 (UTC)
Raw View
Carl Daniel wrote:

> Perhaps I'm missing something obvious, but the use of a mutex to provide MT
> safety in boost::shared_ptr guarantees that it is in fact far less efficient
> than typical COM reference counting, including the virtual function call
> overhead.  If shared_ptr was updated to use InterlockedIncrement, then it
> could be faster than the typical COM AddRef() call due to the virtual
> function call in the latter.

It's actually much more complicated than this due to the
interaction with weak_ptr.  The problem can be seen in the
followng code:

  boost::weak_ptr<T> wp;
  {
    boost::shared_ptr<T> sp( new T ); wp = sp;
  }
  boost::shared_ptr<T> sp( wp.lock() );

In this, wp is assigned a shared_ptr which immediately goes
out of scope.  The wp.lock() function needs to know that the
use count of the now dead shared_ptr is zero.  This means
the use count must be held onto until all weak_ptrs have
been removed, which means keeping count of the number of
active weak_ptrs too.

Therefore, instead of the naive

  template <class T> shared_ptr {
    size_t* ref_count;
    T*      pointer;
  };

you need something like

  struct ref_count_holder {
    size_t shared_count; // num shared_ptrs
    size_t weak_count;   // num weak_ptrs + shared_ptrs
  };

  template <class T> shared_ptr {
    ref_count_holder* ref_counts;
    T*                pointer;
  };

and when the ref_count falls to zero, pointer is deleted,
but the ref_count_holder is not deleted until weak_count
falls to zero.

Why does this complicate matters?  Because now we have two
separate counts to maintain, and I know of no platforms that
provide atomic operations to manipulate a pair of variable
simulatenously -- hence the use of a mutex in the Boost
implementation.

> Worse, COM objects "know" how their reference counts should be maintained.
> A COM object that's "single threaded" can be used in an MT program, and yet
> never use synchronization on access to the reference count (since it's an
> error for multiple threads to ever enter the object).  Thus, for a very
> common case, the boost::shared_ptr mechanism is nearly guaranteed to be less
> efficient than normal COM reference counting.

Quite possibly, but does the COM model allow weak pointers
in the sense used in TR1?  My guess is not, but I confess to
not being particularly fluent with the details of COM.

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Sat, 12 Jul 2003 00:13:36 +0000 (UTC)
Raw View
cpdaniel@nospam.mvps.org ("Carl Daniel") writes:

> "Richard Smith" wrote:
>
>> If you mean that IUnknown::AddRef and IUnknown::Release are virtual
>> and can't be inlined, then you're quite correct.  Boost's reference
>> counting mechanism does not use this -- it has a it's own independent
>> reference count.  Only when Boost's reference count drops to zero
>> does it (virtually) call the custom deletion function, which will
>> call IUnknown::Release.  Thus you can copy boost::shared_ptr's around
>> as much as you want, and you will not incur the overhead of calls to
>> IUnknown::Release until the object is finally destroyed.  The only
>> overhead is from Boost's own reference counting mechanism, which is
>> much more efficient.(And with a bit of hand-crafted assembler to do
>> the atomic incrementing, etc. the mutex in the 1.30 implementation
>> could be removed, making it even more so.  Or perhaps I'm missing
>> something?)
>
> Perhaps I'm missing something obvious, but the use of a mutex to
> provide MT safety in boost::shared_ptr guarantees that it is in fact
> far less efficient than typical COM reference counting, including
> the virtual function call overhead.

Of course, a mutex is only used if multithreading is enabled.

> If shared_ptr was updated to use InterlockedIncrement, then it could
> be faster than the typical COM AddRef() call due to the virtual
> function call in the latter.

Which shows that the cost is a "mere" implementation detail.  Care to
submit a patch?

> Worse, COM objects "know" how their reference counts should be
> maintained.  A COM object that's "single threaded"

Well isn't it actually ``a COM _type_ that's "single threaded"?''

> can be used in an MT program, and yet never use synchronization on
> access to the reference count (since it's an error for multiple
> threads to ever enter the object).  Thus, for a very common case,
> the boost::shared_ptr mechanism is nearly guaranteed to be less
> efficient than normal COM reference counting.

That's an interesting point.  Of course, COM's apartment model is
quite problematic in many cases, and having to decide
single/multi-threadedness per-type instead of per-object can be a
problem too, can't it?

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: eldiener@earthlink.net ("Edward Diener")
Date: Sat, 12 Jul 2003 00:15:43 +0000 (UTC)
Raw View
"Carl Daniel" wrote:
> "Richard Smith" wrote:
>
>> If you mean that IUnknown::AddRef and IUnknown::Release are virtual
>> and can't be inlined, then you're quite correct.  Boost's reference
>> counting mechanism does not use this -- it has a it's own independent
>> reference count.  Only when Boost's reference count drops to zero
>> does it (virtually) call the custom deletion function, which will
>> call IUnknown::Release.  Thus you can copy boost::shared_ptr's around
>> as much as you want, and you will not incur the overhead of calls to
>> IUnknown::Release until the object is finally destroyed.  The only
>> overhead is from Boost's own reference counting mechanism, which is
>> much more efficient.(And with a bit of hand-crafted assembler to do
>> the atomic incrementing, etc. the mutex in the 1.30 implementation
>> could be removed, making it even more so.  Or perhaps I'm missing
>> something?)
>
> Perhaps I'm missing something obvious, but the use of a mutex to
> provide MT safety in boost::shared_ptr guarantees that it is in fact
> far less efficient than typical COM reference counting, including the
> virtual function call overhead.  If shared_ptr was updated to use
> InterlockedIncrement, then it could be faster than the typical COM
> AddRef() call due to the virtual function call in the latter.

Perhaps you are missing the fact that other operating systems other than
Windows exists, and that InterlockedIncrement is not a C++ Standard library
function.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: cpdaniel@nospam.mvps.org ("Carl Daniel")
Date: Sat, 12 Jul 2003 16:11:13 +0000 (UTC)
Raw View
"Edward Diener" wrote:
> "Carl Daniel" wrote:
>> Perhaps I'm missing something obvious, but the use of a mutex to
>> provide MT safety in boost::shared_ptr guarantees that it is in fact
>> far less efficient than typical COM reference counting, including the
>> virtual function call overhead.  If shared_ptr was updated to use
>> InterlockedIncrement, then it could be faster than the typical COM
>> AddRef() call due to the virtual function call in the latter.
>
> Perhaps you are missing the fact that other operating systems other
> than Windows exists, and that InterlockedIncrement is not a C++
> Standard library function.
>

No, not missing that at all.  Merely commenting on a comment about
shared_ptr and COM.  The fact that COM doesn't exist on many OS's in no way
detracts from the discussion.   The fact that InterlockedIncrement is not a
standard library function is similarly irrelevant:  the entire boost::thread
library, from which the mutex is obtained, is built on non-standard library
functions (which follows naturally, since there are no standard library
functions for threading/thread safety).

The core issue, IMO, is not even about COM, but the fact that in an MT-safe
version, boost::shared_ptr uses a mutex to protect the atomic increment of
not one but two counts (see Richard Smith's reply to my posting).  This
means that copying a boost::shared_ptr in an MT program on Windows is
somewhere between 100's to 1000's of times slower than copying a plain
pointer.  I'm sure the cost on a pthreads system will be similarly high.
IMO, that's far too high a price to pay, regardless of whether you're using
it with COM.

-cd

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: terekhov@web.de (Alexander Terekhov)
Date: Sat, 12 Jul 2003 19:44:30 +0000 (UTC)
Raw View
Richard Smith wrote:
[...]
> Why does this complicate matters?  Because now we have two
> separate counts to maintain, and I know of no platforms that
> provide atomic operations to manipulate a pair of variable
> simulatenously -- hence the use of a mutex in the Boost
> implementation.

You don't really need "atomic operations to manipulate a PAIR
of variable simulatenously".

http://terekhov.de/pthread_refcount_t/experimental/refcount.cpp
http://groups.google.com/groups?selm=3EC0F1F1.B78AA0DA%40web.de
(Subject: Re: Atomic ops on Intel: do they sync?)

--
http://google.com/groups?threadm=3F0EBAF7.9E76235E%40web.de
(Subject: Bah!)

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com ("Richard Smith")
Date: Tue, 8 Jul 2003 18:27:26 +0000 (UTC)
Raw View
Gianni Mariani wrote:
>
> I would like to discuss :
>
> http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1450.html

[...]

> The calling of a virtual method to increment and decrement the
> reference count can become a significant performance issue.

Maybe I'm being unobservant, but can you point me to a bit of N1450 that
requires virtual functions for incrementing and decrementing the reference
count (or equivalent)?   Certainly, the Boost 1.30 implementation does not
do a virtual function call in these circumstances.  The boost::shared_ptr
copy constructor uses the implictly generated copy constructor, which calls
down to boost::details::shared_count's copy constructor, which in turn calls
boost::details::sp_counted_base::add_ref, which is not virtual and all ought
to be trivially inlineable.  A similar call sequence happens in the
destructor, which only calls the virtual dispose function when the reference
count reaches zero.

The only use of virtual functions, or calls via function pointers, etc. in
the Boost implementation is in the handling of the deletion function object,
and which is (I believe) unavoidable if the class is to be useable on an
incomplete type.  Also, in my experience, the cost of a virtual function
call is relatively small compared to the cost of deleting an object.

> The best solution is to
> create 3 co-operating smart pointer classes.

You've yet to convince me there's a problem at all, and certainly not one
that would justify adding two new smart pointer types.

>  Once class is intended to
> be for objects that are semi-persistant, the other 2 are for passing or
> returning pointers.  Of the 2 pointer passing templates one is intended
> to imply a policy that the receiver is being passed the responsibility
> to release a reference

This is what std::auto_ptr does when passed by value.

> while the other is intended to imply that no sucj
> responsibility exists.

And this is what passing the raw object by const reference is for.
Alternatively, passing shared_ptr by const reference (or even by value)
would work.

> In practice, this system reduces the errors due to reference counting
> leaks/bugs to virtually zero.

Could you please give an example of the sort of leak you hope to avoid?  In
my experience with boost::shared_ptr, the main source of memory leaks is
when you have cyclic references.  After that, I think I can honestly say, I
get more memory leaks due to compiler bugs than due to misusing
boost::shared_ptr.

> b) Interface for reference counting.
>
> There are a number of different methods names/schemes used for managing
> reference counts.  I suggest that to make the library truly useful, it
> would be best to parameterize the reference counting methods through a
> template argument.  This would enable the use of the smart pointer
> templates with systems like MS-COM as well as inc()/dec() methods.

One of the design principles given in this documentation is [III.A.3]:

| No Extra Parameters
|
| Following the "as close as possible" principle, the proposed smart
| pointers have a single template parameter, the type of the pointee.
| Avoiding additional parameters ensures interoperability between
| libraries from different authors, and also makes shared_ptr easier to
| use, teach and recommend.

Although I'm not overly familiar with COM, my understanding is that by using
a suitable deletion function, a shared_ptr can be allowed to made to hold a
COM object.  Persumably the game is to get the deletion function to call
obj->Release().

This avoids any need to specify the functions to increment and decrement the
reference counts.

> c) Reference to pointer within.
>
> There are older interfaces that expect a pointer to pointer to the
> object to be returned.  It would be most advantageous for a `safe' way
> to access the inner pointer.

  shared_ptr<T> my_ptr;

  {
    T *tmp = my_ptr.get();
    legacy_function(&tmp);

    // Depending on legacy_function's semantics, you might need to
    assert(tmp == my_ptr.get());

    // or
    if (tmp != my_ptr.get())
      my_ptr.reset(tmp);

    // or even
    if (tmp != my_ptr.get()) {
      *my_ptr = *tmp;
      delete tmp;
    }
  }

> I have a working implementation of a smart pointer reference counting
> library that implements all of these features.  I would be more than
> happy to submit it as an alternative smart pointer system for
consideration.

I think a more constructive course of action would be to explain precisely
which bits of the current proposal you are unhappy with and why, and how
exactly you would modify them.  Submitting an entirely new alternative smart
pointer proposal is unlikely to be productive, especially when the existing
proposal is as popular and heavily used as the Boost one.

--
Richard Smith


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Tue, 8 Jul 2003 21:55:31 +0000 (UTC)
Raw View
Richard Smith wrote:
> Gianni Mariani wrote:
>
>>I would like to discuss :
>>
>>http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1450.html
>
>
> [...]
>
>
>>The calling of a virtual method to increment and decrement the
>>reference count can become a significant performance issue.
>
>
> Maybe I'm being unobservant, but can you point me to a bit of N1450 that
> requires virtual functions for incrementing and decrementing the reference
> count (or equivalent)?   Certainly, the Boost 1.30 implementation does not
> do a virtual function call in these circumstances.  The boost::shared_ptr
> copy constructor uses the implictly generated copy constructor, which calls
> down to boost::details::shared_count's copy constructor, which in turn calls
> boost::details::sp_counted_base::add_ref, which is not virtual and all ought
> to be trivially inlineable.  A similar call sequence happens in the
> destructor, which only calls the virtual dispose function when the reference
> count reaches zero.

OK - we might be on different planets here but if we do this to COM
pointers then they are virtual and can't be trivially inlineable.

It makes little sense to make the argument that on one end they are
trivially inlineable and on the other end to say they work with COM
which can't be trivially inlineable.


>
> The only use of virtual functions, or calls via function pointers, etc. in
> the Boost implementation is in the handling of the deletion function object,
> and which is (I believe) unavoidable if the class is to be useable on an
> incomplete type.  Also, in my experience, the cost of a virtual function
> call is relatively small compared to the cost of deleting an object.

Small but potentially significant.  One of the underlining philosophies
in C++ is that you don't pay for what you don't need.  The boost
reference counted model seems (correct me if I am wrong) to require
incrementing and decrementing reference counts when reference counted
objects are trivially passed or retruned.  I suggest that this is
directly contradicting the philosophy stated.

>
>
>>The best solution is to
>>create 3 co-operating smart pointer classes.
>
>
> You've yet to convince me there's a problem at all, and certainly not one
> that would justify adding two new smart pointer types.
>
>
>> Once class is intended to
>>be for objects that are semi-persistant, the other 2 are for passing or
>>returning pointers.  Of the 2 pointer passing templates one is intended
>>to imply a policy that the receiver is being passed the responsibility
>>to release a reference
>
>
> This is what std::auto_ptr does when passed by value.

I don't get what auto_ptr has to do with this discussion.  Please elaborate.

>
>
>>while the other is intended to imply that no sucj
>>responsibility exists.
>
>
> And this is what passing the raw object by const reference is for.
> Alternatively, passing shared_ptr by const reference (or even by value)
> would work.

But passing a const pointer has nothing to do with policies regarding
obligation for decrementing reference count.

>
>
>>In practice, this system reduces the errors due to reference counting
>>leaks/bugs to virtually zero.
>
>
> Could you please give an example of the sort of leak you hope to avoid?

Yep, when passing a reference counted pointer and the obligation to
decrement the reference count and no decrement is done by the callee (or
similary when returning a reference counted pointer).

  In
> my experience with boost::shared_ptr, the main source of memory leaks is
> when you have cyclic references.  After that, I think I can honestly say, I
> get more memory leaks due to compiler bugs than due to misusing
> boost::shared_ptr.
>

Yes, and multiple useless calls to reference counting methods.

In tight loops you want to avoid this and if you are limited to using
raw pointers you diminish greatly the value of what the smart pointers
are doing for you.

>
>>b) Interface for reference counting.
>>
>>There are a number of different methods names/schemes used for managing
>>reference counts.  I suggest that to make the library truly useful, it
>>would be best to parameterize the reference counting methods through a
>>template argument.  This would enable the use of the smart pointer
>>templates with systems like MS-COM as well as inc()/dec() methods.
>
>
> One of the design principles given in this documentation is [III.A.3]:
>
> | No Extra Parameters
> |
> | Following the "as close as possible" principle, the proposed smart
> | pointers have a single template parameter, the type of the pointee.
> | Avoiding additional parameters ensures interoperability between
> | libraries from different authors, and also makes shared_ptr easier to
> | use, teach and recommend.
>
> Although I'm not overly familiar with COM, my understanding is that by using
> a suitable deletion function, a shared_ptr can be allowed to made to hold a
> COM object.  Persumably the game is to get the deletion function to call
> obj->Release().
>
> This avoids any need to specify the functions to increment and decrement the
> reference counts.

I don't get it OR this is just plain FUD.  You can certainly make it so
the template has default paramater values and hence you get the benefit
described above yet it is trivial to use where you're applying these
templates to reference counting interfaces that pre-exist (like COM).

Why limit the ultility of the smart pointer classes ?

>
>
>>c) Reference to pointer within.
>>
>>There are older interfaces that expect a pointer to pointer to the
>>object to be returned.  It would be most advantageous for a `safe' way
>>to access the inner pointer.
>
>
>   shared_ptr<T> my_ptr;
>
>   {
>     T *tmp = my_ptr.get();
>     legacy_function(&tmp);
>
>     // Depending on legacy_function's semantics, you might need to
>     assert(tmp == my_ptr.get());
>
>     // or
>     if (tmp != my_ptr.get())
>       my_ptr.reset(tmp);
>
>     // or even
>     if (tmp != my_ptr.get()) {
>       *my_ptr = *tmp;
>       delete tmp;
>     }
>   }
>

I think we're talking about different things.

>
>>I have a working implementation of a smart pointer reference counting
>>library that implements all of these features.  I would be more than
>>happy to submit it as an alternative smart pointer system for
>
> consideration.
>
> I think a more constructive course of action would be to explain precisely
> which bits of the current proposal you are unhappy with and why, and how
> exactly you would modify them.  Submitting an entirely new alternative smart
> pointer proposal is unlikely to be productive, especially when the existing
> proposal is as popular and heavily used as the Boost one.

Before I go and invest my time doing such a thing, I would prefer that
this discussion have some interest.

Just like std::auto_ptr, I think there is danger that c++ developers
will find significant issues with the boost shared pointers if a more
hearty discussion does not occur.

In simple terms, I'm saying "I have some ideas that may make sense, if
you want me to explain it further, then show me you're interested in
dicussing it".

So far, it seems like people (other than yourself) care very little.

G

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Wed, 9 Jul 2003 02:43:09 +0000 (UTC)
Raw View
gi2nospam@mariani.ws (Gianni Mariani) wrote in message news:<beaj87$c19@dispatch.concentric.net>...
> I would like to discuss :
>
> http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1450.html
>
> I would like to see 3 more features in the libraries regarding smart
> pointers, in particular reference counting smart pointers.
>
>
> a) Returning or passing smart pointers.
>
> A number of issues occur when passing or returning pointers. The most
> critical one is when in a tight loop and passing a reference counted
> pointer.  The calling of a virtual method to increment and decrement the
> reference count can become a significant performance issue.

There is no requirement that reference count updates need to be
implemented with virtual member functions.

> Passing the raw pointer produces confusing semantics.

Not necessarily. A shared_ptr parameter implies that the function
needs to be able to take ownership. If this is not the case, a
reference or a raw pointer (depending on whether NULL is a valid
argument) is more appropriate. Otherwise, passing shared_ptr by const
reference may eliminate the copy.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 9 Jul 2003 02:45:48 +0000 (UTC)
Raw View
gi2nospam@mariani.ws (Gianni Mariani) writes:

> Richard Smith wrote:
>> Gianni Mariani wrote:
>>
>>>I would like to discuss :
>>>
>>>http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1450.html
>> [...]
>>
>>>The calling of a virtual method to increment and decrement the
>>>reference count can become a significant performance issue.
>>
>> Maybe I'm being unobservant, but can you point me to a bit of N1450
>> that
>> requires virtual functions for incrementing and decrementing the reference
>> count (or equivalent)?   Certainly, the Boost 1.30 implementation does not
>> do a virtual function call in these circumstances.  The boost::shared_ptr
>> copy constructor uses the implictly generated copy constructor, which calls
>> down to boost::details::shared_count's copy constructor, which in turn calls
>> boost::details::sp_counted_base::add_ref, which is not virtual and all ought
>> to be trivially inlineable.  A similar call sequence happens in the
>> destructor, which only calls the virtual dispose function when the reference
>> count reaches zero.
>
> OK - we might be on different planets here but if we do this to COM
> pointers then they are virtual and can't be trivially inlineable.
>
> It makes little sense to make the argument that on one end they are
> trivially inlineable and on the other end to say they work with COM
> which can't be trivially inlineable.

This seems to be a perfect argument for the Boost model.  What great
advantage is there in inlining all of a shared_ptr destructor which
calls a non-inlinable COM destructor?

>> The only use of virtual functions, or calls via function pointers,
>> etc. in the Boost implementation is in the handling of the deletion
>> function object, and which is (I believe) unavoidable if the class
>> is to be useable on an incomplete type.  Also, in my experience,
>> the cost of a virtual function call is relatively small compared to
>> the cost of deleting an object.
>
> Small but potentially significant.

Have you got an application where you've measured it to be
significant?

> One of the underlining philosophies in C++ is that you don't pay for
> what you don't need.  The boost reference counted model seems
> (correct me if I am wrong) to require incrementing and decrementing
> reference counts when reference counted objects are trivially passed
> or retruned.

Only when passed.  The compiler is free to apply the RVO in most
situations where a shared_ptr is returned.

> I suggest that this is directly contradicting the philosophy stated.

It's the only safe possibility other than (arguably) auto_ptr, given
the current core language definition.  If you need to not pay for
reference counting when objects are passed, you should use an auto_ptr
or a raw pointer (or show us a better alternative).

>>>while the other is intended to imply that no sucj responsibility
>>>exists.
>>
>> And this is what passing the raw object by const reference is for.
>> Alternatively, passing shared_ptr by const reference (or even by value)
>> would work.
>
> But passing a const pointer has nothing to do with policies regarding
> obligation for decrementing reference count.

If I write:

   foo(shared_ptr<T> const&);

Then I can pass any shared_ptr<T> to foo without changing its
reference count.  There is no obligation to change its reference
count.

>>>In practice, this system reduces the errors due to reference counting
>>>leaks/bugs to virtually zero.

You don't need these reductions with boost::shared_ptr because the
errors you claim to be preventing don't occur with boost::shared_ptr.

>> Could you please give an example of the sort of leak you hope to
>> avoid?
>
> Yep, when passing a reference counted pointer and the obligation to
> decrement the reference count and no decrement is done by the callee
> (or similary when returning a reference counted pointer).

Those sorts of problems are the result of your "reference counted
pointer" model which automates too little.

>> In my experience with boost::shared_ptr, the main source of memory
>> leaks is when you have cyclic references.  After that, I think I
>> can honestly say, I get more memory leaks due to compiler bugs than
>> due to misusing boost::shared_ptr.
>>
>
> Yes, and multiple useless calls to reference counting methods.
>
> In tight loops you want to avoid this and if you are limited to using
> raw pointers you diminish greatly the value of what the smart pointers
> are doing for you.

Likewise if you have to keep track of when a shared_ptr's pointee may
have become invalid, which is essentially what your proposal seems to
require.  After you pass it to a function which takes one of your
reference-stealing pointers as a parameter, you can't use it anymore.

>>>b) Interface for reference counting.
>>>
>>>There are a number of different methods names/schemes used for managing
>>>reference counts.  I suggest that to make the library truly useful, it
>>>would be best to parameterize the reference counting methods through a
>>>template argument.  This would enable the use of the smart pointer
>>>templates with systems like MS-COM as well as inc()/dec() methods.
>>
>> One of the design principles given in this documentation is
>> [III.A.3]:
>> | No Extra Parameters
>> |
>> | Following the "as close as possible" principle, the proposed smart
>> | pointers have a single template parameter, the type of the pointee.
>> | Avoiding additional parameters ensures interoperability between
>> | libraries from different authors, and also makes shared_ptr easier to
>> | use, teach and recommend.
>>
>> Although I'm not overly familiar with COM, my understanding is that
>> by using a suitable deletion function, a shared_ptr can be allowed
>> to made to hold a COM object.  Persumably the game is to get the
>> deletion function to call
>> obj->Release().
>> This avoids any need to specify the functions to increment and
>> decrement the reference counts.
>
> I don't get it OR this is just plain FUD.  You can certainly make it
> so the template has default paramater values and hence you get the
> benefit described above

No you don't.  If the parameter is there people will use it and you
will have many different types of smart pointer to the same T, which
don't interoperate.  An extra parameter also impairs learnability.

> yet it is trivial to use where you're applying these templates to
> reference counting interfaces that pre-exist (like COM).
>
> Why limit the ultility of the smart pointer classes ?

There's no limitation; you don't need to use COM's slow refcounting
when you can use shared_ptr's fast refcounting.

>>>c) Reference to pointer within.
>>>
>>>There are older interfaces that expect a pointer to pointer to the
>>>object to be returned.  It would be most advantageous for a `safe' way
>>>to access the inner pointer.

Ouch.  The only safe pointer to the contained T* of a shared_ptr is a
T*const*.  What legacy interface benefits from providing that?

>>>I have a working implementation of a smart pointer reference
>>>counting library that implements all of these features.  I would be
>>>more than happy to submit it as an alternative smart pointer system
>>>for consideration.
>>
>> I think a more constructive course of action would be to explain
>> precisely which bits of the current proposal you are unhappy with
>> and why, and how exactly you would modify them.  Submitting an
>> entirely new alternative smart pointer proposal is unlikely to be
>> productive, especially when the existing proposal is as popular and
>> heavily used as the Boost one.

And already accepted into the TR.

> Before I go and invest my time doing such a thing, I would prefer that
> this discussion have some interest.

You've got several responses now.

> Just like std::auto_ptr, I think there is danger that c++ developers
> will find significant issues with the boost shared pointers if a
> more hearty discussion does not occur.

Let's have it, then!

> In simple terms, I'm saying "I have some ideas that may make sense,
> if you want me to explain it further, then show me you're interested
> in dicussing it".
>
> So far, it seems like people (other than yourself) care very little.

I think you mistake strong disagreement for disinterest.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Wed, 9 Jul 2003 02:46:28 +0000 (UTC)
Raw View
In article <bef891$c1h@dispatch.concentric.net>, Gianni Mariani
<gi2nospam@mariani.ws> wrote:

| So far, it seems like people (other than yourself) care very little.

Fwiw, I care a lot.

I'm intimately familiar with the tr1::shared_ptr having independently
implemented it.  After reading your post, it seemed like you had not
looked closely at shared_ptr, and there was no reference to enable me
to look more closely at your design.

My day is simply too full to drop everything everytime somebody says:
I've got a better idea, want to know more?

It literally took me 6 months to schedule the two weeks it took to
crawl inside of tr1::shared_ptr enough to feel like I understood it.
I'm probably slow, but that's what it takes me.

If you've got a better idea, and if you feel strongly enough about it,
post it somewhere so we can study it at our convenience.  And since
std::tr1::shared_ptr is already there, it would be to your advantage to
compare and contrast with it (with code examples!) in order to shorten
your reader's learning curve.

--
Howard Hinnant
Metrowerks

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Wed, 9 Jul 2003 18:41:35 +0000 (UTC)
Raw View
This is a multi-part message in MIME format.
--------------060801080700020909020800
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Howard Hinnant wrote:
> In article <bef891$c1h@dispatch.concentric.net>, Gianni Mariani
> <gi2nospam@mariani.ws> wrote:
>
> | So far, it seems like people (other than yourself) care very little.
>
> Fwiw, I care a lot.
>
> I'm intimately familiar with the tr1::shared_ptr having independently
> implemented it.  After reading your post, it seemed like you had not
> looked closely at shared_ptr, and there was no reference to enable me
> to look more closely at your design.

See attachment.

>
> My day is simply too full to drop everything everytime somebody says:
> I've got a better idea, want to know more?

Hint of arrogance I hear ?

>
> It literally took me 6 months to schedule the two weeks it took to
> crawl inside of tr1::shared_ptr enough to feel like I understood it.
> I'm probably slow, but that's what it takes me.



>
> If you've got a better idea, and if you feel strongly enough about it,
> post it somewhere so we can study it at our convenience.  And since
> std::tr1::shared_ptr is already there, it would be to your advantage to
> compare and contrast with it (with code examples!) in order to shorten
> your reader's learning curve.
>

I have attached the header that defines the templates.  If you're
interested I'll make an alpha release of the entire library soon as it
stands soon.




--------------060801080700020909020800
Content-Type: text/plain;
 name="at_lifetime.h"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="at_lifetime.h"

/**
 * at_lifetime.h
 *
 */

#ifndef __at_lifetime_h__
#define __at_lifetime_h__ 1

#include "at_assert.h"
#include "at_types.h"


// ======== Forward references =========================================
//

template < class w_ClassRef > class AT_ReferenceTraits;

#define AT_Friend_LifeTime \
 template < \
  class wa_ClassRef, \
  class wa_RefTraits \
 > friend class AT_LifeTime; \
// end macro

template <
    class w_ClassRef,
    class w_RefTraits = AT_ReferenceTraits< w_ClassRef >
> class AT_LifeTime;

#define AT_Friend_LifeView \
 template < \
  class wa_ClassRef, \
  class wa_RefTraits \
 > friend class AT_LifeView; \
// end macro


template <
    class w_ClassRef,
    class w_RefTraits = AT_ReferenceTraits< w_ClassRef >
> class AT_LifeView;


// ======== AT_LifeControl ===========================================
/**
 * AT_LifeControl is an abstract base class for any class whose lifetime
 * is managed by means of reference counting.
 */

class AT_LifeControl
{
    public:

 virtual ~AT_LifeControl();  // Virtual destructor

    /**
     * AddRef()
     * --------
     * This method adds 1 to the reference count to this object.
     *
     * @return  ret  The current reference count.
     */

    virtual int AddRef() = 0;

    /**
     * Release()
     * ---------
     * This decrements by 1 the reference count to this object.  Once
     * all references have been released (the reference count is zero),
  * the AT_LifeControl implementation is expected to delete itself.
  * The act of "deleting itself" may be determinted to be different
  * for each kind of implementation.
     *
     * @return  ret  The current reference count.
     */

    virtual int Release() = 0;
};


// ======== AT_ReferenceTraits =========================================
/**
 * AT_ReferenceTraits is a helper class that wraps AT_LifeControl's
 * AddRef() and Release() methods inside static methods having the
 * standard names IncRefCount() and DecRefCount().
 *
 * If you are using your own reference-counting scheme, i.e. one not
 * based on AT_LifeControl, then you will need to define your own
 * helper class that implements the same interface as AT_ReferenceTraits.
 *
 * This is necessary because an AT_ReferenceTraits-like class is expected
 * as the second template parameter in the definitions of smart pointers
 * concerned with lifetime management.
 */

template <class w_ClassRef>
class AT_ReferenceTraits
{
    public:

    /**
     * IncRefCount()
     * -------------
     * This method increments the reference count of an object.
     *
     * @param  i_ptr  A pointer to the reference-counted object.
     */

    static inline void IncRefCount( w_ClassRef i_ptr )
    {
        i_ptr->AddRef();
    }

    /**
     * DecRefCount()
     * -------------
     * This method decrements the reference count of an object.
     * Once the reference count reaches zero, the object will
     * delete itself.
     *
     * @param  i_ptr  A pointer to the reference-counted object.
     */

    static inline void DecRefCount( w_ClassRef i_ptr )
    {
        i_ptr->Release();
    }
};


// ======== AT_LifeLine ================================================
/**
 * The AT_LifeLine smart pointer is designed to solve a very common
 * problem:  When you pass an object's pointer to a function, how do
 * you tell that function that he is supposed to RELEASE the object
 * when he's done with it?
 *
 * With an ordinary pointer, the best you can do is to insert a comment
 * to that effect in the code.  The problem with this approach is that
 * if a programmer doesn't read the comment, he may very well neglect
 * to release the object, thereby causing a memory leak.
 *
 * A better solution is to use a MECHANISM that enforces your desired
 * POLICY.  The mechanism in this case is the AT_LifeLine smart
 * pointer.
 *
 * By declaring the passed pointer's formal parameter type to be an
 * AT_LifeLine, you GUARANTEE that the function either releases the
 * object, or copies its pointer to an AT_LifeTime persistent pointer
 * so that it can be released later.
 *
 * How does this happen?  Very simple:  When the function returns,
 * the AT_LifeLine formal parameter goes out of scope, causing its
 * destructor to be called.  The destructor verifies that the object
 * either has been released, or has been copied to a persistent
 * pointer.
 *
 * In effect, the AT_LifeLine forces the function to take OWNERSHIP
 * of the pointed-to object, including the responsibility for
 * releasing the object when he is done with it.
 *
 * (Incidentally, the name "AT_LifeLine" is meant to evoke the image
 * of someone tossing a rope to someone adrift at sea, saying, in
 * effect, "Here, this is yours, you'd better take ownership of it.")
 */

/**
 * Instrumenting an AT_LifeLine object to check for memory leaks is a
 * useful debugging aid, but is expensive in terms of execution time.
 *
 * Therefore, the code that checks for memory leaks will be conditionally
 * compiled.
 */

#define __AT_MEMORY_LEAK_CHECK  \
    ( AT_FORCE_MEMORY_LEAK_CHECK || ! RELEASE_BUILD )

/**
 * g_abortOnMemoryLeak
 * -------------------
 * This global flag specifies the action to take if a memory leak
 * is detected:  'true' = Abort, 'false' = Throw exception.  (Can
 * be useful for unit tests that want to keep on going if a leak
 * is detected.)
 */

extern bool g_abortOnMemoryLeak;

/**
 * The instrumented and un-instrumented versions of the AT_LifeLine
 * class will be given different mangled names, so that if the two
 * versions are accidentally co-mingled in an application, the linker
 * stands a chance of catching the error.
 */

#if __AT_MEMORY_LEAK_CHECK

#define AT_Friend_LifeLine \
    template < \
        class wa_ClassRef, \
        class wa_RefTraits, \
        class wa_MemoryLeak \
    > friend class AT_LifeLine; \
// end macro

    template <
        class w_ClassRef,
        class w_RefTraits = AT_ReferenceTraits< w_ClassRef >,
        class w_MemoryLeak = int
    >

#else

#define AT_Friend_LifeLine \
    template < \
        class wa_ClassRef, \
        class wa_RefTraits \
    > friend class AT_LifeLine; \
// end macro

    template <
        class w_ClassRef,
        class w_RefTraits = AT_ReferenceTraits< w_ClassRef >
    >

#endif // __AT_MEMORY_LEAK_CHECK

class AT_LifeLine
{
 AT_Friend_LifeView
 AT_Friend_LifeTime
 AT_Friend_LifeLine

        /**
         * m_ptrVal
         * --------
         * This data member contains the actual pointer value,
         * i.e. the address of the object whose lifetime is
         * being managed by reference counting.
         */

        w_ClassRef m_ptrVal;

    private:

#if __AT_MEMORY_LEAK_CHECK

        /**
         * m_hasBeenTransferred
         * --------------------
         * This data member contains a flag indicating whether
         * ownership of the pointee has been successfully
         * transferred to some persistent pointer.
         * (Declared "mutable" so it can be changed even if
         * this AT_LifeLine pointer was declared "const".)
         *
         * Since initialization of data members is not permitted
         * in class definitions, we use a helper class to create
         * the data member and initialize it to 'false'.
         */

        mutable AT_IType< bool, false > m_hasBeenTransferred;

        /**
         * MadeTransfer()
         * --------------
         * This method sets a member variable indicating that
         * ownership of the pointee has been successfully
         * transferred from this AT_LifeLine to some persistent
         * pointer.
         */

        inline void MadeTransfer() const
        {
            Assert( ! m_hasBeenTransferred.m_value );
            m_hasBeenTransferred.m_value = true;
        }

        /**
         * CheckTransfer()
         * ---------------
         * This method checks whether ownership of the pointee
         * has been successfully transferred to some persistent
         * pointer.
         */

        inline void CheckTransfer() const
        {
            if ( m_ptrVal != 0 ) {
    // the '.m_value' is to work around
    // a compiler bug
                Assert( m_hasBeenTransferred.m_value );
            }
        }

        /**
         * SetTransfer()
         * -------------
         * This method sets the 'm_hasBeenTransferred' data
         * member to the specified value.
         */

        inline void SetTransfer( bool val )
        {
            m_hasBeenTransferred = val;
        }

        /**
         * Assert()
         * --------
         * This method aborts or throws an exception if a given
         * condition fails.
         */

        private: inline void Assert( bool l_condition ) const
        {
            if ( ! l_condition ) {
                if ( g_abortOnMemoryLeak ) {
                    AT_Assert( false );
                }
                else {
                    throw "Memory Leak exception";
                }
            }
        }

#else // __AT_MEMORY_LEAK_CHECK == false

  // These versions of the methods are
  // empty - meaning there is no instrumentation
  //

        inline void MadeTransfer() const  {};
        inline void CheckTransfer() {};
        inline void SetTransfer()   {};

#endif // __AT_MEMORY_LEAK_CHECK


    public:

        /**
         * AT_LifeLine( AT_LifeLine<> )
         * ----------------------------
         * This method contructs an AT_LifeLine from another
         * AT_LifeLine which may be based on a different under-
         * lying pointer type.
         *
         * @param  i_ptr  A reference to the AT_LifeLine pointer
         *                being copied.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline AT_LifeLine(
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Indicate that ownership of the pointee has been
            // transferred to the new AT_LifeLine pointer.
            // -----------------------------------------------
            i_ptr.MadeTransfer();
        }

        /**
         * AT_LifeLine( AT_LifeLine )
         * --------------------------
         * This method explicitly provides a copy constructor for
         * AT_LifeLine.  Apparently, without such a method, the
         * compiler is unable (or unwilling) to use the above
         * templated constructor, and instead generates its own
         * code that does the wrong thing.
         *
         * @param  i_ptr  A reference to the AT_LifeLine pointer
         *                being copied.
         */

        inline AT_LifeLine(
            const AT_LifeLine< w_ClassRef, w_RefTraits > & i_ptr
  )
          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Indicate that ownership of the pointee has been
            // transferred to the new AT_LifeLine pointer.
            // -----------------------------------------------
            i_ptr.MadeTransfer();
        }

        /**
         * AT_LifeLine( AT_LifeTime )
         * --------------------------
         * This method contructs an AT_LifeLine pointer from an
         * AT_LifeTime pointer.
         *
         * @param  i_ptr  A reference to the AT_LifeTime pointer
         *                being copied.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeLine(
            const AT_LifeTime < w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Increment the pointee's reference count.
            // ----------------------------------------
            if ( m_ptrVal ) {
                w_RefTraits::IncRefCount( m_ptrVal );
            }
        }

        /**
         * AT_LifeLine( AT_LifeView )
         * --------------------------
         * This method contructs an AT_LifeLine from an AT_LifeView.
         *
         * @param  i_ptr  A reference to the AT_LifeView pointer
         *                being copied.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeLine(
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Increment the pointee's reference count.
            // ----------------------------------------
            if ( m_ptrVal ) {
                w_RefTraits::IncRefCount( m_ptrVal );
            }
        }

        /**
         * AT_LifeLine( dumb pointer )
         * ---------------------------
         * This method contructs an AT_LifeLine from a dumb pointer.
         *
         * @param  i_ptr  The dumb pointer being copied (optional).
         */

        inline AT_LifeLine( w_ClassRef i_ptr = 0 )
          : m_ptrVal( i_ptr )
        {
        }

        /**
         * AT_LifeLine( dumb pointer, bool )
         * ---------------------------------
         * This method contructs an AT_LifeLine from a dumb pointer,
         * optionally transferring ownership of the pointee to the
         * AT_LifeLine pointer.
         *
         * @param  i_ptr            The dumb pointer being copied.
         *
         * @param  i_takeOwnership  A flag indicating whether owner-
         *                          ship of the pointee should be
         *                          transferred to the AT_LifeLine
         *                          pointer.
         */

        inline AT_LifeLine( w_ClassRef i_ptr, bool i_takeOwnership )
          : m_ptrVal( i_ptr )
        {
            if ( i_takeOwnership ) {
                if ( i_ptr ) {
                    w_RefTraits::IncRefCount( i_ptr );
                }
            }
        }

        /**
         * ~AT_LifeLine()
         * --------------
         * Destructor.
         */

        inline ~AT_LifeLine()
        {
            // Verify that ownership of the pointee has been
            // transferred to some persistent pointer.
            // ---------------------------------------------
            CheckTransfer();
        }

        /**
         * AT_LifeLine = dumb pointer
         * --------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeLine is being set equal to a
         * dumb pointer.
         *
         * @param  i_ptr  The dumb pointer on the right-hand side
         *                of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeLine on
         *                the left-hand side of the '=' sign.
         */

        inline AT_LifeLine & operator= ( const w_ClassRef i_ptr )
        {
            // Verify that ownership of the AT_LifeLine's pointee
            // has already been transferred to some persistent
            // pointer.
            // --------------------------------------------------
            CheckTransfer();

            // Copy the dumb pointer to the AT_LifeLine.
            // -----------------------------------------
            m_ptrVal = i_ptr;

            // Indicate that ownership of the new pointee has not
            // yet been transferred to a persistent pointer.
            // --------------------------------------------------
            SetTransfer( false );

            // Return a reference to the AT_LifeLine, so that
            // assignment operations can be chained together.
            // ----------------------------------------------
            return *this;
        }

        /**
         * AT_LifeLine = AT_LifeLine< >
         * ----------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeLine is being set equal to another
         * AT_LifeLine.
         *
         * @param  i_ptr  A reference to the AT_LifeLine on the
         *                right-hand side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeLine on the
         *                left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline AT_LifeLine & operator= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraits > & i_ptr
  )
        {
            // Tell the source AT_LifeLine that ownership of its
            // pointee is being transferred.
            // -------------------------------------------------
            i_ptr.MadeTransfer();

            // Verify that ownership of the destination AT_LifeLine's
            // pointee has already been transferred to some persistent
            // pointer.
            // -------------------------------------------------------
            CheckTransfer();

            // Copy the pointer value from source to dest.
            // -------------------------------------------
            m_ptrVal = i_ptr.m_ptrVal;

            // Indicate that ownership of the destination pointee
            // has not yet been transferred to some persistent
            // pointer.
            // --------------------------------------------------
            SetTransfer( false );

            // Return a reference to the destination AT_LifeLine,
            // so that assignment operations can be chained together.
            // ------------------------------------------------------
            return * this;
        }

        /**
         * AT_LifeLine = AT_LifeTime
         * -------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeLine is being set equal to an
         * AT_LifeTime.
         *
         * @param  i_ptr  A reference to the AT_LifeTime on the
         *                right-hand side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeLine on the
         *                left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeLine & operator= (
            const AT_LifeTime<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr
  )
        {
            // Verify that ownership of the AT_LifeLine's pointee
            // has already been transferred to some persistent
            // pointer.
            // --------------------------------------------------
            CheckTransfer();

            // Copy the pointer.
            // -----------------
            m_ptrVal = i_ptr.m_ptrVal;

            // Increment the new pointee's reference count.
            // --------------------------------------------
            w_RefTraits::IncRefCount( m_ptrVal );

            // Indicate that the ownership of the new pointee has
            // not yet been transferred to a persistent pointer.
            // --------------------------------------------------
            SetTransfer( false );

            // Return a reference to the AT_LifeLine, so that
            // assignment operations can be chained together.
            // ----------------------------------------------
            return *this;
        }

        /**
         * AT_LifeLine = AT_LifeView
         * -------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeLine is being set equal to an
         * AT_LifeView.
         *
         * @param  i_ptr  A reference to the AT_LifeView on the
         *                right-hand side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeLine on the
         *                left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeLine & operator= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraits > & i_ptr
  )
        {
            // Verify that ownership of the AT_LifeLine's pointee
            // has already been transferred to some persistent
            // pointer.
            // --------------------------------------------------
            CheckTransfer();

            // Copy the pointer.
            // -----------------
            m_ptrVal = i_ptr.m_ptrVal;

            // Increment the new pointee's reference count.
            // --------------------------------------------
            w_RefTraits::IncRefCount( m_ptrVal );

            // Indicate that the ownership of the new pointee has
            // not yet been transferred to a persistent pointer.
            // --------------------------------------------------
            SetTransfer( false );

            // Return a reference to the AT_LifeLine, so that
            // assignment operations can be chained together.
            // ----------------------------------------------
            return *this;
        }

        /**
         * Transfer()
         * ----------
         * This helper method marks this AT_LifeLine as having had
         * its pointee's ownership successfully transferred to a
         * persistent pointer, and returns the pointee's address.
         *
         * @return  ret  The value of the AT_LifeLine pointer,
         *               i.e. the address of the pointee.
         */

        inline w_ClassRef Transfer() const
        {
            MadeTransfer();
            return m_ptrVal;
        }

        /**
         * LifeView()
         * ----------
         * This helper method returns an AT_LifeView version of
         * this AT_LifeLine pointer.
         *
         * @return  ret  An AT_LifeView version of this AT_Life-
         *               Line pointer.
         */

        inline AT_LifeView< w_ClassRef, w_RefTraits > LifeView()
            const
        {
            return m_ptrVal;
        }

        /**
         * AT_LifeLine->
         * -------------
         * This method overloads the dereference operator.
         *
         * @return  ret  The value of the AT_LifeLine smart pointer,
         *               i.e. the address of the object whose life-
         *               time is being managed by reference counting.
         */

        inline w_ClassRef operator-> () const
        {
            return m_ptrVal;
        }

        /**
         * w_ClassRef( AT_LifeLine )
         * -------------------------
         * This method overloads the cast-to-<w_ClassRef> operator.
         *
         * @return  ret  The value of the AT_LifeLine smart pointer,
         *               i.e. the address of the object whose life-
         *               time is being managed by reference counting.
         */

        inline operator w_ClassRef () const
        {
            return m_ptrVal;
        }

        /**
         * bool( AT_LifeLine )
         * -------------------
         * This method overloads the cast-to-bool operator.
         *
         * @return  ret  A boolean indicating whether (true) or not
         *               (false) the AT_LifeLine pointer is non-NULL.
         */

        inline operator bool () const
        {
            return m_ptrVal != 0;
        }

        /**
         * ! AT_LifeLine
         * -------------
         * This method overloads the ! operator.
         *
         * @return  ret  A boolean indicating whether (true) or not
         *               (false) the AT_LifeLine pointer is NULL.
         */

        inline bool operator! () const
        {
            return m_ptrVal == 0;
        }

        /**
         * AT_LifeLine == AT_LifeLine
         * AT_LifeLine == AT_LifeTime
         * AT_LifeLine == AT_LifeView
         * --------------------------
         * These methods overload the == operator for the cases where
         * an AT_LifeLine is being tested for equality to an AT_Life-
         * Line, an AT_LifeTime, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '==' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the two pointers on each
         *                side of the '==' sign are equal, i.e.
         *                point to the same object.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator== (
   const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator== (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator== (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeLine != AT_LifeLine
         * AT_LifeLine != AT_LifeTime
         * AT_LifeLine != AT_LifeView
         * --------------------------
         * These methods overload the != operator for the cases
         * where an AT_LifeLine is being tested for inequality
         * to an AT_LifeLine, an AT_LifeTime, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '!=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the two pointers on each
         *                side of the '!=' sign are unequal, i.e.
         *                point to different objects.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator!= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator!= (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator!= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeLine < AT_LifeLine
         * AT_LifeLine < AT_LifeTime
         * AT_LifeLine < AT_LifeView
         * -------------------------
         * These methods overload the < operator for the cases
         * where an AT_LifeLine is being tested to see if it is
         * less than an AT_LifeLine, an AT_LifeTime, or an
         * AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '<' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is less
         *                than the right-hand pointer, i.e. whether
         *                the address of the left-hand pointee is
         *                less than the address of the right-hand
         *                pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator< (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator< (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator< (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeLine > AT_LifeLine
         * AT_LifeLine > AT_LifeTime
         * AT_LifeLine > AT_LifeView
         * -------------------------
         * These methods overload the > operator for the cases
         * where an AT_LifeLine is being tested to see if it is
         * greater than an AT_LifeLine, an AT_LifeTime, or an
         * AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '>' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is
         *                greater than the right-hand pointer,
         *                i.e. whether the address of the left-
         *                hand pointee is greater than the
         *                address of the right-hand pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator> (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator> (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator> (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeLine <= AT_LifeLine
         * AT_LifeLine <= AT_LifeTime
         * AT_LifeLine <= AT_LifeView
         * --------------------------
         * These methods overload the <= operator for the cases
         * where an AT_LifeLine is being tested to see if it is
         * less than or equal to an AT_LifeLine, an AT_LifeTime,
         * or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '<=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is
         *                less than or equal to the right-hand
         *                pointer, i.e. whether the address of
         *                the left-hand pointee is less than or
         *                equal to the address of the right-hand
         *                pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeLine >= AT_LifeLine
         * AT_LifeLine >= AT_LifeTime
         * AT_LifeLine >= AT_LifeView
         * --------------------------
         * These methods overload the >= operator for the cases
         * where an AT_LifeLine is being tested to see if it is
         * greater than or equal to an AT_LifeLine, an AT_Life-
         * Time, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '>=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is
         *                greater than or equal to the right-hand
         *                pointer, i.e. whether the address of
         *                the left-hand pointee is greater than
         *                or equal to the address of the right-
         *                hand pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator>= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator>= (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator>= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }
};


// ======== AT_LifeTime ================================================
/**
 * The AT_LifeTime smart pointer is designed to make it easier to
 * work with an object whose lifetime is managed by means of ref-
 * erence counting.
 *
 * With an ordinary pointer, you must remember to call AddRef()
 * when you start using the object, and Release() when you are
 * done with the object.
 *
 * The problem is that it is very easy to forget to do an AddRef()
 * or a Release(), especially when errors occur.
 *
 * An AT_LifeTime smart pointer solves this problem, by doing the
 * AddRef() and Release() for you automatically.
 *
 * How is this accomplished?  Very simply:  When an AT_LifeTime
 * pointer is copied or assigned to another one, the constructor
 * or overloaded assignment operator automatically does an AddRef().
 * Similarly, when an AT_LifeTime pointer is deleted or goes out of
 * scope, its destructor automatically does a Release().
 */

template < class w_ClassRef, class w_RefTraits >
class AT_LifeTime
{
 AT_Friend_LifeView
 AT_Friend_LifeTime
 AT_Friend_LifeLine

        /**
         * m_ptrVal
         * --------
         * This data member contains the actual pointer value,
         * i.e. the address of the object whose lifetime is
         * being managed by reference counting.
         */

        w_ClassRef m_ptrVal;

    private:
        /**
         * ReleasePointee()
         * ----------------
         * This method releases the pointee, i.e. decrements
         * its reference count.
         */

        inline void ReleasePointee()
        {
            if ( m_ptrVal ) {
                w_RefTraits::DecRefCount( m_ptrVal );
            }
        }

        /**
         * ReleasePointee( new dumb pointer )
         * ----------------------------------
         * This method releases the existing pointee, i.e.
         * decrements its reference count, and "adopts" a
         * new one.
         *
         * @param  i_ptrVal  A dumb pointer to the new pointee.
         * @return ret       A dumb pointer to the new pointee.
         */

        inline w_ClassRef ReleasePointee( w_ClassRef i_ptrVal )
        {
            // Save a pointer to the old pointee.
            // ----------------------------------
            w_ClassRef l_ptrVal = m_ptrVal;

            // Point to the new pointee.
            // -------------------------
            m_ptrVal = i_ptrVal;

            // Decrement the old pointee's reference count.
            // --------------------------------------------
            if ( l_ptrVal ) {
                w_RefTraits::DecRefCount( l_ptrVal );
            }

            // Return a dumb pointer to the new pointee.
            // -----------------------------------------
            return i_ptrVal;
        }

    public:

        /**
         * AT_LifeTime( dumb pointer )
         * ---------------------------
         * This method contructs an AT_LifeTime from a dumb pointer.
         *
         * @param  i_ptr  The dumb pointer to be copied (optional).
         */

        inline AT_LifeTime( w_ClassRef i_ptr = 0 ) : m_ptrVal( i_ptr )
        {
        }

        /**
         * AT_LifeTime( dumb pointer, bool )
         * ---------------------------------
         * This method constructs an AT_LifeTime smart pointer from
         * a dumb pointer, optionally taking ownership of the pointee
         * (by bumping his reference count).
         *
         * @param  ptr              The dumb pointer to be copied.
         *
         * @param  i_takeOwnership  A flag indicating whether or not
         *                          to take ownership of the pointee.
         */

        inline AT_LifeTime( w_ClassRef ptr, bool i_takeOwnership )
          : m_ptrVal( ptr )
        {
            if ( i_takeOwnership ) {
                if ( ptr ) {
                    w_RefTraits::IncRefCount(ptr);
                }
            }
        }

        /**
         * AT_LifeTime( AT_LifeTime<> )
         * ----------------------------
         * This method contructs an AT_LifeTime from another
         * AT_LifeTime which may be based on a different under-
         * lying pointer type.
         *
         * @param  i_ptr  A reference to the AT_LifeTime being
         *                copied.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline AT_LifeTime(
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Bump the pointee's reference count.
            // -----------------------------------
            if ( m_ptrVal ) {
                w_RefTraits::IncRefCount(m_ptrVal);
            }
        }

        /**
         * AT_LifeTime( AT_LifeTime )
         * --------------------------
         * This method provides an explicit copy constructor for
         * AT_LifeTime.  Apparently, without such a constructor,
         * the compiler is unable (or unwilling) to use the above
         * templated constructor, and instead generates its own
         * code that does the wrong thing.
         *
         * @param  i_ptr  A reference to the AT_LifeTime pointer
         *                being copied.
         */

        inline AT_LifeTime(
            const AT_LifeTime< w_ClassRef, w_RefTraits > & i_ptr
  )
          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Bump the pointee's reference count.
            // -----------------------------------
            if ( m_ptrVal ) {
                w_RefTraits::IncRefCount(m_ptrVal);
            }
        }

        /**
         * AT_LifeTime( AT_LifeView )
         * --------------------------
         * This method contructs an AT_LifeTime from an AT_LifeView.
         *
         * @param  i_ptr  A reference to the AT_LifeView being copied.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline AT_LifeTime(
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Bump the pointee's reference count.
            // -----------------------------------
            if ( m_ptrVal ) {
                w_RefTraits::IncRefCount(m_ptrVal);
            }
        }

        /**
         * AT_LifeTime( AT_LifeLine )
         * --------------------------
         * This method contructs an AT_LifeTime from an AT_LifeLine.
         *
         * @param  i_ptr  The AT_LifeLine smart pointer being copied.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline AT_LifeTime(
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Mark the AT_LifeLine to indicate that ownership
            // of its pointee has been successfully transferred
            // to a persistent pointer.
            // ------------------------------------------------
            i_ptr.MadeTransfer();
        }

        /**
         * ~AT_LifeTime()
         * --------------
         * Destructor.
         */

        inline ~AT_LifeTime()
        {
            // Releases the pointee.
            // ---------------------
            ReleasePointee();
        }

        /**
         * AT_LifeTime = dumb pointer
         * --------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeTime is being set equal to a dumb
         * pointer.
         *
         * @param  i_ptr  The dumb pointer on the right-hand side
         *                of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeTime pointer
         *                on the left-hand side of the '=' sign.
         */

        inline AT_LifeTime< w_ClassRef, w_RefTraits > & operator= (
   w_ClassRef    i_ptr
  )
        {
            // Release the old pointee, and adopt the new one.
            // -----------------------------------------------
            ReleasePointee( i_ptr );

            // Return a reference to the AT_LifeTime pointer on
            // the left-hand side of the '=' sign, so that assign-
            // ment operations can be chained together.
            // ---------------------------------------------------
   return *this;
        }

        /**
         * AT_LifeTime = AT_LifeTime< >
         * ----------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeTime is being set equal to another
         * AT_LifeTime which may be based on a different underlying
         * pointer type.
         *
         * @param  i_ptr  A reference to the AT_LifeTime pointer
         *                on the right-hand side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeTime pointer
         *                on the left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeTime< w_ClassRef, w_RefTraits > & operator=(
   const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            // Save the new pointer.
            // ---------------------
            w_ClassRef l_ptrVal = i_ptr.m_ptrVal;

            // Increment the reference count of the new pointee.
            // -------------------------------------------------
            if ( l_ptrVal ) {
                w_RefTraits::IncRefCount( l_ptrVal );
            }

            // Release the old pointee, and adopt the new one in
            // its place.
            // -------------------------------------------------
            ReleasePointee( l_ptrVal );

            // Return a reference to the AT_LifeTime pointer on
            // the left-hand side of the '=' sign, so that assign-
            // ment operations can be chained together.
            // ---------------------------------------------------
            return *this;
        }

        /**
         * AT_LifeTime = AT_LifeTime
         * -------------------------
         * This method explicitly overloads the assignment operator
         * for the case where an AT_LifeTime is being set equal to
         * another AT_LifeTime, and both AT_LifeTimes point to the
         * same type of reference-counted object.
         *
         * Apparently, if such a method is not provided, the compiler
         * is unable (or unwilling) to use the above template version
         * of overloaded assignment, and instead generates its own
         * code that does the wrong thing.
         *
         * @param  i_ptr  A reference to the AT_LifeTime pointer on
         *                the right-hand side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeTime pointer on
         *                the left-hand side of the '=' sign.
         */

        inline AT_LifeTime< w_ClassRef, w_RefTraits > & operator= (
            const AT_LifeTime< w_ClassRef, w_RefTraits > & i_ptr
  )
        {
            // Save the new pointer.
            // ---------------------
            w_ClassRef l_ptrVal = i_ptr.m_ptrVal;

            // Increment the reference count of the new pointee.
            // -------------------------------------------------
            if ( l_ptrVal ) {
                w_RefTraits::IncRefCount( l_ptrVal );
            }

            // Release the old pointee, and adopt the new one in
            // its place.
            // -------------------------------------------------
            ReleasePointee( l_ptrVal );

            // Return a reference to the AT_LifeTime pointer on
            // the left-hand side of the '=' sign, so that assign-
            // ment operations can be chained together.
            // ---------------------------------------------------
            return *this;
        }

        /**
         * AT_LifeTime = AT_LifeView
         * -------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeTime is being set equal to an
         * AT_LifeView.
         *
         * @param  i_ptr  A reference to the AT_LifeView pointer
         *                on the right-hand side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeTime pointer
         *                on the left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline AT_LifeTime< w_ClassRef, w_RefTraits > & operator=(
   const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            // Save the new pointer.
            // ---------------------
            w_ClassRef l_ptrVal = i_ptr.m_ptrVal;

            // Increment the reference count of the new pointee.
            // -------------------------------------------------
            if ( l_ptrVal ) {
                w_RefTraits::IncRefCount( l_ptrVal );
            }

            // Release the old pointee, and adopt the new one in
            // its place.
            // -------------------------------------------------
            ReleasePointee( l_ptrVal );

            // Return a reference to the AT_LifeTime pointer on
            // the left-hand side of the '=' sign, so that assign-
            // ment operations can be chained together.
            // ---------------------------------------------------
            return *this;
        }

        /**
         * AT_LifeTime = AT_LifeLine
         * -------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeTime is being set equal to an
         * AT_LifeLine.
         *
         * @param  i_ptr  A reference to the AT_LifeLine pointer
         *                on the right-hand side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeTime pointer
         *                on the left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeTime< w_ClassRef, w_RefTraits > & operator=(
   const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            // Mark the AT_LifeLine as having successfully trans-
            // ferred ownership of its pointee to a persistent
            // pointer, release the AT_LifeTime's reference to
            // its current pointee, and adopt the AT_LifeLine's
            // pointee.
            // --------------------------------------------------
            ReleasePointee( i_ptr.Transfer() );

            // Return a reference to the AT_LifeTime pointer on
            // the left-hand side of the '=' sign, so that assign-
            // ment operations can be chained together.
            // ---------------------------------------------------
            return *this;
        }

        /**
         * AT_LifeTime->
         * -------------
         * This method overloads the dereference operator.
         *
         * @return  ret  The address of the pointee.
         */

        inline w_ClassRef operator-> () const
        {
            return m_ptrVal;
        }

        /**
         * InnerReference()
         * ----------------
         * This method releases the pointee, and returns the
         * address of the private data member that is used to
         * store the address of the pointee.  (USE WITH CARE)
         *
         * @return  ret  The address of the private data member
         *               used to store the address of the pointee.
         */

        inline w_ClassRef * InnerReference()
        {
            // Release the pointee.
            // --------------------
            ReleasePointee( 0 );

            // Return the address of the private data member used
            // to store the address of the pointee.
            // --------------------------------------------------
            return & m_ptrVal;
        }

        /**
         * w_ClassRef()
         * -------------------------
         * This method overloads the cast-to-<w_ClassRef> operator.
         *
         * @return  ret  The address of the pointee.
         */

        inline operator w_ClassRef () const
        {
            return m_ptrVal;
        }

        /**
         * bool( AT_LifeTime )
         * -------------------
         * This method overloads the cast-to-bool operator.
         *
         * @return  ret  A flag indicating whether (true) or not
         *               (false) the pointee's address is non-NULL.
         */

        inline operator bool () const
        {
            return m_ptrVal != 0;
        }

        /**
         * Adopt( dumb pointer )
         * ---------------------
         * This helper method "adopts" the pointee of a dumb
         * pointer, adding a reference to it in the process.
         *
         * @param  i_ptr  The address of the new pointee.
         * @return ret    The address of the new pointee.
         */

        inline w_ClassRef Adopt( const w_ClassRef i_ptr )
        {
            // Add a reference to the dumb pointer's pointee.
            // ----------------------------------------------
            if ( i_ptr ) {
                w_RefTraits::IncRefCount( i_ptr );
            }

            // Set this AT_LifeTime equal to the dumb pointer.
            // -----------------------------------------------
            ( * this ) = i_ptr;

            return i_ptr;
        }

        /**
         * Adopt( AT_LifeLine )
         * --------------------
         * This helper method transfers ownership of a pointee
         * from an AT_LifeLine pointer to this AT_LifeTime pointer.
         *
         * @param  i_ptr  A reference to the AT_LifeLine pointer
         *                whose pointee is to be "adopted".
         *
         * @return ret    The address of the "adopted" pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline w_ClassRef Adopt(
            const AT_LifeLine<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr
  )
        {
            // Mark the AT_LifeLine pointer as having success-
            // fully transferred the ownership of its pointee
            // to a persistent pointer.
            // -----------------------------------------------
            i_ptr.MadeTransfer();

            // Set this AT_LifeTime equal to the AT_LifeLine.
            // ----------------------------------------------
            ( * this ) = i_ptr;

            return i_ptr;
        }

        /**
         * Transfer()
         * ----------
         * This helper method returns the address of the pointee,
         * and then sets it to zero.
         *
         * @return  ret  The address of the pointee.
         */

        inline w_ClassRef Transfer()
        {
            w_ClassRef l_ptr = m_ptrVal;

            m_ptrVal = 0;

            return l_ptr;
        }

        /**
         * LifeView()
         * ----------
         * This helper method returns an AT_LifeView version of
         * this AT_LifeTime pointer.
         *
         * @return  ret  An AT_LifeView version of this AT_Life-
         *               Time pointer.
         */

        inline AT_LifeView< w_ClassRef > LifeView() const
        {
            return m_ptrVal;
        }

        /**
         * ! AT_LifeTime
         * -------------
         * This method overloads the ! operator.
         *
         * @return  ret  A boolean indicating whether (true) or
         *               not (false) this AT_LifeTime pointer
         *               is zero.
         */

        inline bool operator ! () const
        {
            return m_ptrVal == 0;
        }

        /**
         * AT_LifeTime == AT_LifeLine
         * AT_LifeTime == AT_LifeTime
         * AT_LifeTime == AT_LifeView
         * --------------------------
         * These methods overload the '==' operator for the cases
         * where an AT_LifeTime is being tested for equality to an
         * AT_LifeLine, an AT_LifeTime, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '==' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the two pointers on each
         *                side of the '==' sign are equal, i.e.
         *                point to the same object.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator== (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator== (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator== (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeTime != AT_LifeLine
         * AT_LifeTime != AT_LifeTime
         * AT_LifeTime != AT_LifeView
         * --------------------------
         * These methods overload the '!=' operator for the cases
         * where an AT_LifeTime is being tested for inequality to
         * an AT_LifeLine, an AT_LifeTime, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '!=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the two pointers on each
         *                side of the '!=' sign are unequal, i.e.
         *                point to different objects.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator!= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator!= (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator!= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeTime < AT_LifeLine
         * AT_LifeTime < AT_LifeTime
         * AT_LifeTime < AT_LifeView
         * -------------------------
         * These methods overload the < operator for the cases
         * where an AT_LifeTime is being tested to see if it is
         * less than an AT_LifeLine, an AT_LifeTime, or an
         * AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '<' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is less
         *                than the right-hand pointer, i.e. whether
         *                the address of the left-hand pointee is
         *                less than the address of the right-hand
         *                pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator< (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator< (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator< (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeTime > AT_LifeLine
         * AT_LifeTime > AT_LifeTime
         * AT_LifeTime > AT_LifeView
         * -------------------------
         * These methods overload the > operator for the cases
         * where an AT_LifeTime is being tested to see if it is
         * greater than an AT_LifeLine, an AT_LifeTime, or an
         * AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '>' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is
         *                greater than the right-hand pointer,
         *                i.e. whether the address of the left-
         *                hand pointee is greater than the
         *                address of the right-hand pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator> (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator> (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator> (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeTime <= AT_LifeLine
         * AT_LifeTime <= AT_LifeTime
         * AT_LifeTime <= AT_LifeView
         * --------------------------
         * These methods overload the <= operator for the cases
         * where an AT_LifeTime is being tested to see if it is
         * less than or equal to an AT_LifeLine, an AT_LifeTime,
         * or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '<=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is less
         *                than or equal to the right-hand pointer,
         *                i.e. whether the address of the left-hand
         *                pointee is less than or equal to the
         *                address of the right-hand pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeTime >= AT_LifeLine
         * AT_LifeTime >= AT_LifeTime
         * AT_LifeTime >= AT_LifeView
         * --------------------------
         * These methods overload the >= operator for the cases
         * where an AT_LifeTime is being tested to see if it is
         * greater than or equal to an AT_LifeLine, an AT_Life-
         * Time, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '>=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is
         *                greater than or equal to the right-
         *                hand pointer, i.e. whether the address
         *                of the left-hand pointee is greater
         *                than or equal to the address of the
         *                right-hand pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator>= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator>= (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator>= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }
};


// ======== AT_LifeView ================================================
/**
 * The AT_LifeView smart pointer is intended to be used in a very
 * specific situation:  You need to pass an object's smart pointer
 * to a function so the function can do something with the object.
 *
 * However, you know the function will have no further need of the
 * object once it returns, and you want the caller to retain owner-
 * ship of the object (including the responsibility for releasing
 * it eventually).
 *
 * If you pass the object's pointer as an AT_LifeTime, this will
 * cause an implicit AddRef() to be done on the object on entry to
 * the function, and an implicit Release() on return from the func-
 * tion.  While this isn't incorrect, it is unnecessary for this
 * kind of function, and thus adds a performance penalty for no
 * reason.
 *
 * The solution to this problem is to declare the passed pointer's
 * formal parameter type to be an AT_LifeView.  This type of pointer
 * will NOT do an implicit AddRef() or Release() on the object.
 *
 * (Incidentally, the name "AT_LifeView" is meant to evoke the image
 * of someone handing an expensive vase to someone else and saying,
 * in effect, "Here's an interesting object.  You can look at it
 * for a little while, but then you have to give it right back.")
 *
 * To be complete however, there is nothing stopping an AT_LifeView
 * recipient to copy an AT_LifeView to an AT_LifeTime pointer which
 * will perform an implicit AddRef().
 */

template < class w_ClassRef, class w_RefTraits >

class AT_LifeView
{
 AT_Friend_LifeView
 AT_Friend_LifeTime
 AT_Friend_LifeLine

        /**
         * m_ptrVal
         * --------
         * This data member contains the actual pointer value,
         * i.e. the address of the object whose lifetime is
         * being managed by reference counting.
         */

        w_ClassRef m_ptrVal;

    public:

        /**
         * AT_LifeView( dumb pointer )
         * ---------------------------
         * This method contructs an AT_LifeView smart pointer
         * from a dumb pointer.
         *
         * @param  i_ptr  The dumb pointer (optional).
         */

        inline AT_LifeView( w_ClassRef i_ptr = 0 )
          : m_ptrVal( i_ptr )
        {
        }

        /**
         * AT_LifeView( AT_LifeView<> )
         * ----------------------------
         * This method constructs an AT_LifeView from another
         * AT_LifeView which may be based on a different under-
         * lying pointer type.
         *
         * @param  i_ptr  A reference to the AT_LifeView from which
         *                this one is being constructed.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeView(
            const AT_LifeView<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr )

          : m_ptrVal( i_ptr.m_ptrVal )
        {
        }

        /**
         * AT_LifeView( AT_LifeView )
         * --------------------------
         * This method provides an explicit copy constructor
         * for AT_LifeView.  Apparently, without such a con-
         * structor, the compiler is unable (or unwilling)
         * to use the above templated constructor, and instead
         * generates its own code that does the wrong thing.
         *
         * @param  i_ptr  A reference to the AT_LifeView pointer
         *                being copied.
         */

        inline AT_LifeView(
            const AT_LifeView<
                w_ClassRef,
                w_RefTraits
            > & i_ptr )

          : m_ptrVal( i_ptr.m_ptrVal )
        {
        }

        /**
         * AT_LifeView( AT_LifeTime )
         * --------------------------
         * This method constructs an AT_LifeView from an
         * AT_LifeTime.
         *
         * @param  i_ptr  A reference to the AT_LifeTime that
         *                is to be copied.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeView(
            const AT_LifeTime<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr )

          : m_ptrVal( i_ptr.m_ptrVal )
        {
        }

        /**
         * AT_LifeView( AT_LifeLine )
         * --------------------------
         * This method constructs an AT_LifeView from an
         * AT_LifeLine.
         *
         * @param  i_ptr  A reference to the AT_LifeLine that
         *                is to be copied.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeView(
            const AT_LifeLine<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr )

          : m_ptrVal( i_ptr.m_ptrVal )
        {
        }

        /**
         * ~AT_LifeView
         * ------------
         * Destructor.
         */

        inline ~AT_LifeView()
        {
        }

        /**
         * AT_LifeView = dumb pointer
         * --------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeView is being set equal to a dumb
         * pointer.
         *
         * @param  i_ptr  The dumb pointer on the right-hand
         *                side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeView pointer
         *                on the left-hand side of the '=' sign.
         */

        inline AT_LifeView< w_ClassRef, w_RefTraits > &
            operator= ( const w_ClassRef i_ptr )
        {
            m_ptrVal = i_ptr;
            return *this;
        }

        /**
         * AT_LifeView = AT_LifeView
         * -------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeView is being set equal to another
         * AT_LifeView.
         *
         * @param  i_ptr  The AT_LifeView on the right-hand side
         *                of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeView on the
         *                left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeView< w_ClassRef, w_RefTraits > &
            operator= (
            const AT_LifeView<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr )
        {
            m_ptrVal = i_ptr.m_ptrVal;
            return *this;
        }

        /**
         * AT_LifeView = AT_LifeTime
         * --------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeView is being set equal to an
         * AT_LifeTime.
         *
         * @param  i_ptr  A reference to the AT_LifeTime on the
         *                right-hand side of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeView on the
         *                left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline AT_LifeView< w_ClassRef, w_RefTraits > &
            operator= (
            const AT_LifeTime<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr )
        {
            m_ptrVal = i_ptr.m_ptrVal;
            return *this;
        }

        /**
         * AT_LifeView = AT_LifeLine
         * -------------------------
         * This method overloads the assignment operator for the
         * case where an AT_LifeView is being set equal to an
         * AT_LifeLine.
         *
         * @param  i_ptr  The AT_LifeLine on the right-hand side
         *                of the '=' sign.
         *
         * @return ret    A reference to the AT_LifeView on the
         *                left-hand side of the '=' sign.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline AT_LifeView< w_ClassRef, w_RefTraits > &
            operator= (
            const AT_LifeLine<
                w_ClassRefRhs,
                w_RefTraits
            > & i_ptr )
        {
            m_ptrVal = static_cast< w_ClassRef >( i_ptr );
            return *this;
        }

        /**
         * AT_LifeView->
         * -------------
         * This method overloads the dereference operator.
         *
         * @return  ret  The address of the pointed-to object.
         */

        inline w_ClassRef operator->() const
        {
            return m_ptrVal;
        }

        /**
         * w_ClassRef( AT_LifeView )
         * -------------------------
         * This method overloads the cast-to-w_ClassRef operator.
         *
         * @return  ret  The address of the pointed-to object.
         */

        inline operator w_ClassRef() const
        {
            return m_ptrVal;
        }

        /**
         * LifeView()
         * ----------
         * This method returns an AT_LifeView version of this
         * pointer.
         *
         * @return  ret  An AT_LifeView version of this pointer.
         */

        inline AT_LifeView< w_ClassRef > LifeView() const
        {
            return *this;
        }

        /**
         * bool( AT_LifeView )
         * -------------------
         * This method overloads the cast-to-bool operator.
         *
         * @return  ret  A boolean indicating whether (true) or not
         *               (false) the pointee's address is non-zero.
         */

        inline operator bool() const
        {
            return m_ptrVal != 0;
        }

        /**
         * ! ( AT_LifeView )
         * -----------------
         * This method overloads the ! operator.
         *
         * @return  ret  A boolean indicating whether (true) or not
         *               (false) the pointee's address is zero.
         */
        inline bool operator! () const
        {
            return m_ptrVal == 0;
        }

        /**
         * AT_LifeView == AT_LifeLine
         * AT_LifeView == AT_LifeTime
         * AT_LifeView == AT_LifeView
         * --------------------------
         * These methods overload the '==' operator for the cases
         * where an AT_LifeView is being tested for equality to an
         * AT_LifeLine, an AT_LifeTime, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '==' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the two pointers on each
         *                side of the '==' sign are equal, i.e.
         *                point to the same object.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator== (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator== (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator== (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal == i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeView != AT_LifeLine
         * AT_LifeView != AT_LifeTime
         * AT_LifeView != AT_LifeView
         * --------------------------
         * These methods overload the '!=' operator for the cases
         * where an AT_LifeView is being tested for inequality to
         * an AT_LifeLine, an AT_LifeTime, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '!=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the two pointers on each
         *                side of the '!=' sign are unequal, i.e.
         *                point to different objects.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator!= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator!= (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator!= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal != i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeView < AT_LifeLine
         * AT_LifeView < AT_LifeTime
         * AT_LifeView < AT_LifeView
         * -------------------------
         * These methods overload the < operator for the cases
         * where an AT_LifeView is being tested to see if it is
         * less than an AT_LifeLine, an AT_LifeTime, or an
         * AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '<' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is less
         *                than the right-hand pointer, i.e. whether
         *                the address of the left-hand pointee is
         *                less than the address of the right-hand
         *                pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator< (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator< (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator< (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal < i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeView > AT_LifeLine
         * AT_LifeView > AT_LifeTime
         * AT_LifeView > AT_LifeView
         * -------------------------
         * These methods overload the > operator for the cases
         * where an AT_LifeView is being tested to see if it is
         * greater than an AT_LifeLine, an AT_LifeTime, or an
         * AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '>' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is
         *                greater than the right-hand pointer,
         *                i.e. whether the address of the left-
         *                hand pointee is greater than the
         *                address of the right-hand pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator> (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator> (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator> (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal > i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeView <= AT_LifeLine
         * AT_LifeView <= AT_LifeTime
         * AT_LifeView <= AT_LifeView
         * --------------------------
         * These methods overload the <= operator for the cases
         * where an AT_LifeView is being tested to see if it is
         * less than or equal to an AT_LifeLine, an AT_LifeTime,
         * or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '<=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is less
         *                than or equal to the right-hand pointer,
         *                i.e. whether the address of the left-hand
         *                pointee is less than or equal to the
         *                address of the right-hand pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeLine< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeTime< w_ClassRefRhs, w_RefTraitsRhs> & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >
        inline bool operator<= (
            const AT_LifeView< w_ClassRefRhs, w_RefTraitsRhs > & i_ptr
  )
        {
            return m_ptrVal <= i_ptr.m_ptrVal;
        }

        /**
         * AT_LifeView >= AT_LifeLine
         * AT_LifeView >= AT_LifeTime
         * AT_LifeView >= AT_LifeView
         * --------------------------
         * These methods overload the >= operator for the cases
         * where an AT_LifeView is being tested to see if it is
         * greater than or equal to an AT_LifeLine, an AT_Life-
         * Time, or an AT_LifeView.
         *
         * @param  i_ptr  A reference to the smart pointer on the
         *                right-hand side of the '>=' operator.
         *
         * @return ret    A boolean indicating whether (true) or
         *                not (false) the left-hand pointer is
         *                greater than or equal to the right-
         *                hand pointer, i.e. whether the address
         *                of the left-hand pointee is greater
         *                than or equal to the address of the
         *                right-hand pointee.
         */

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator>= (
            const AT_LifeLine<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator>= (
            const AT_LifeTime<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }

        template < class w_ClassRefRhs, class w_RefTraitsRhs >

        inline bool operator>= (
            const AT_LifeView<
                w_ClassRefRhs,
                w_RefTraitsRhs
            > & i_ptr )
        {
            return m_ptrVal >= i_ptr.m_ptrVal;
        }
};


// ======== AT_Pointer =================================================
/**
 * The AT_Pointer smart pointer works essentially like the auto_ptr
 * of the Standard Template Library (STL).
 *
 * Unlike the other smart pointers, an AT_Pointer never adds or
 * releases a reference to its pointee.  Therefore, one should use
 * an AT_Pointer only if the pointee is not capable of reference
 * counting.  Otherwise, use an AT_LifeTime.
 */

template < class w_ClassRef >

class AT_Pointer
{
    public:

        /**
         * m_ptrVal
         * --------
         * This data member contains the actual pointer value,
         * i.e. the address of the pointee.
         */

        mutable w_ClassRef m_ptrVal;

    private:

        /**
         * DeletePointee()
         * ---------------
         * This method deletes the pointee.
         */

        inline void DeletePointee()
        {
            if ( m_ptrVal ) {
                delete m_ptrVal;
            }
        }

        /**
         * DeletePointee( new dumb pointer )
         * ---------------------------------
         * This method deletes the existing pointee, and
         * "adopts" a new one.
         *
         * @param  i_ptrVal  A dumb pointer to the new pointee.
         * @return ret       A dumb pointer to the new pointee.
         */

        inline w_ClassRef DeletePointee( w_ClassRef i_ptrVal )
        {
            // Save a pointer to the old pointee.
            // ----------------------------------
            w_ClassRef l_ptrVal = m_ptrVal;

            // Point to the new pointee.
            // -------------------------
            m_ptrVal = i_ptrVal;

            // Delete the old pointee.
            // -----------------------
            if ( l_ptrVal ) {
                delete l_ptrVal;
            }

            // Return a dumb pointer to the new pointee.
            // -----------------------------------------
            return i_ptrVal;
        }

    public:

        /**
         * AT_Pointer( dumb pointer )
         * --------------------------
         * This method constructs an AT_Pointer from a dumb
         * pointer.
         *
         * @param  i_ptr  The dumb pointer from which this
         *                AT_Pointer is to be constructed.
         *                (If no dumb pointer is specified,
         *                then zero is used.)
         */

        inline AT_Pointer( w_ClassRef i_ptr = 0 )
          : m_ptrVal( i_ptr )
        {
        }

        /**
         * AT_Pointer( AT_Pointer )
         * ------------------------
         * Copy constructor.  This method constructs an
         * AT_Pointer from another AT_Pointer.
         *
         * @param  i_ptr  A reference to the AT_Pointer to be
         *                copied.
         */

        inline AT_Pointer(
            const AT_Pointer< w_ClassRef > & i_ptr )

          : m_ptrVal( i_ptr.m_ptrVal )
        {
            // Zero out the pointee address in the AT_Pointer
            // being copied, so that it is impossible to delete
            // the pointee twice accidentally.
            // ------------------------------------------------
            i_ptr.m_ptrVal = 0;
        }

        /**
         * ~AT_Pointer()
         * -------------
         * Destructor.
         */

        inline ~AT_Pointer()
        {
            DeletePointee();
        }

        /**
         * AT_Pointer = dumb pointer
         * -------------------------
         * This method overloads the assignment operator for the
         * case where an AT_Pointer is being set equal to a dumb
         * pointer.
         *
         * @param   i_ptr  The dumb pointer on the right-hand
         *                 side of the '=' sign.
         *
         * @return  ret    A reference to this AT_Pointer (the
         *                 one on the left-hand side of the '='
         *                 sign).
         */

        inline AT_Pointer & operator= ( const w_ClassRef i_ptr )
        {
            // Delete the old pointee, and replace it with the
            // new one.
            // -----------------------------------------------
            DeletePointee( i_ptr );

            // Return a reference to the AT_Pointer on the left-
            // hand side of the '=' sign, so that assignment
            // operations can be chained together.
            // ----------------------------------------------
            return *this;
        }

        /**
         * AT_Pointer = AT_Pointer
         * -----------------------
         * This method overloads the assignment operator for the
         * case where an AT_Pointer is being set equal to another
         * AT_Pointer.
         *
         * @param   i_ptr  A reference to the AT_Pointer on the
         *                 right-hand side of the '=' sign.
         *
         * @return  ret    A reference to this AT_Pointer (the
         *                 one on the left-hand side of the '='
         *                 sign).
         */

        inline AT_Pointer & operator= (
            const AT_Pointer< w_ClassRef > & i_ptr )
        {
            // If the two AT_Pointers both point to the same
            // pointee, then just return.
            // ---------------------------------------------
            if ( & m_ptrVal == & i_ptr.m_ptrVal ) {
                return *this;
            }

            // Save the address of the new pointee.
            // ------------------------------------
            w_ClassRef l_ptrVal = i_ptr.m_ptrVal;

            // Zero out the pointee's address in the AT_Pointer
            // on the right-hand side of the '=' sign, so that
            // the pointee can't be deleted twice accidentally.
            // ------------------------------------------------
            i_ptr.m_ptrVal = 0;

            // Delete the old pointee, and store the new pointee's
            // address in the AT_Pointer on the left-hand side of
            // the '=' sign.
            // ---------------------------------------------------
            DeletePointee( l_ptrVal );

            // Return a reference to the AT_Pointer on the left-
            // hand side of the '=' sign, so that assignment
            // operations can be chained together.
            // ----------------------------------------------
            return *this;
        }

        /**
         * AT_Pointer->
         * ------------
         * This method overloads the dereference operator.
         *
         * @return  ret  The address of the pointee.
         */

        inline w_ClassRef operator-> () const
        {
            return m_ptrVal;
        }

        /**
         * w_ClassRef( AT_Pointer )
         * ------------------------
         * This method overloads the cast-to-w_ClassRef operator.
         *
         * @return  ret  The address of the pointee.
         */

        inline operator w_ClassRef () const
        {
            return m_ptrVal;
        }

        /**
         * bool( AT_Pointer )
         * ------------------
         * This method overloads the cast-to-bool operator.
         *
         * @return  ret  A boolean indicating whether (true) or
         *               not (false) the pointee's address is
         *               non-zero.
         */

        inline operator bool () const
        {
            return m_ptrVal != 0;
        }

        /**
         * Transfer()
         * ----------
         * This helper method returns the pointee's address,
         * and then zeroes it out.
         *
         * @return  ret  The pointee's address.
         */

        inline w_ClassRef Transfer()
        {
            w_ClassRef l_ptrVal = m_ptrVal;

            m_ptrVal = 0;

            return l_ptrVal;
        }
};


#undef AT_Friend_LifeTime
#undef AT_Friend_LifeView
#undef AT_Friend_LifeLine

#endif // __at_lifetime_h__


--------------060801080700020909020800--

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: brangdon@cix.co.uk (Dave Harris)
Date: Wed, 9 Jul 2003 18:41:52 +0000 (UTC)
Raw View
gi2nospam@mariani.ws (Gianni Mariani) wrote (abridged):
> OK - we might be on different planets here but if we do this to COM
> pointers then they are virtual and can't be trivially inlineable.
>
> It makes little sense to make the argument that on one end they are
> trivially inlineable and on the other end to say they work with COM
> which can't be trivially inlineable.

There are two counts. When boost::shared_ptr wraps a COM object, it
allocates a second count, and does all its increments and decrements to
that - inline. The original COM count is only decremented (out of line)
when the boost count hits zero.

In fact, boost always allocates its own count. So you pay the time and
space costs of the allocation, even if you are wrapping a COM object, or
an object with intrusive count, which has no need of it. This is one of
the main downside of boost::shared_ptr, in my view.

If you're not familiar with the boost pointer, it is worth reading:
  http://www.boost.org/libs/smart_ptr/sp_techniques.html


> The boost reference counted model seems (correct me if I am wrong)
> to require incrementing and decrementing reference counts when
> reference counted objects are trivially passed or retruned.

I agree it would be nice if cases like:

    {
        boost::shared_ptr<int> p( new int );
        func( p );
    }

could be optimised so that p's count is never more than 1. As far as I can
tell, the main things preventing this are the use_count() and unique()
members. Without these, many increments and decrements could be omitted
under the "as if" rule. I imagine this was not an issue when shared_ptr
was a standalone library, but now that is becoming part of the standard
perhaps the trade-offs should be reconsidered.

You seem to be advocating a kind of manual optimisation - using a
different kind of pointer when passing or returning. That sounds
cumbersome and error-prone to me. I'd rather the optimisation be done
automatically, by the compiler, when it can prove it is safe.


-- Dave Harris, Nottingham, UK

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 9 Jul 2003 21:25:27 +0000 (UTC)
Raw View
gi2nospam@mariani.ws (Gianni Mariani) writes:

>         /**
>          * MadeTransfer()
>          * --------------
>          * This method sets a member variable indicating that
>          * ownership of the pointee has been successfully
>          * transferred from this AT_LifeLine to some persistent
>          * pointer.
>          */
>
>         inline void MadeTransfer() const
>         {
>             Assert( ! m_hasBeenTransferred.m_value );
>             m_hasBeenTransferred.m_value = true;
>         }
>

Why is setting m_hasBeenTransferred to true better than incrementing
a reference count?  It doesn't seem like you can achieve much of a
savings here.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Wed, 9 Jul 2003 21:58:05 +0000 (UTC)
Raw View
Dave Harris wrote:

>
> You seem to be advocating a kind of manual optimisation - using a
> different kind of pointer when passing or returning. That sounds
> cumbersome and error-prone to me. I'd rather the optimisation be done
> automatically, by the compiler, when it can prove it is safe.

It may "sound" cumbersome but it actually models programmer intentions
cleanly.  Hence, I think you'd be surprised as to how simple it is.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: petebecker@acm.org (Pete Becker)
Date: Thu, 10 Jul 2003 00:41:42 +0000 (UTC)
Raw View
Howard Hinnant wrote:
>
> It literally took me 6 months to schedule the two weeks it took to
> crawl inside of tr1::shared_ptr enough to feel like I understood it.
> I'm probably slow, but that's what it takes me.
>

No, not really slow. I'm figuring one to two weeks (not full time,
though). But not until I've finished random number generators. <g>

--
To delight in war is a merit in the soldier,
a dangerous quality in the captain, and a
positive crime in the statesman.
 George Santayana

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: richard@ex-parrot.com ("Richard Smith")
Date: Thu, 10 Jul 2003 03:48:36 +0000 (UTC)
Raw View
Gianni Mariani wrote:
> Richard Smith wrote:
> > Gianni Mariani wrote:

[...]

> > Maybe I'm being unobservant, but can you point me to a bit of N1450 that
> > requires virtual functions for incrementing and decrementing the
reference
> > count (or equivalent)?   Certainly, the Boost 1.30 implementation does
not
> > do a virtual function call in these circumstances. [...]
>
> OK - we might be on different planets here but if we do this to COM
> pointers then they are virtual and can't be trivially inlineable.

If you mean that IUnknown::AddRef and IUnknown::Release are virtual and
can't be inlined, then you're quite correct.  Boost's reference counting
mechanism does not use this -- it has a it's own independent reference
count.  Only when Boost's reference count drops to zero does it (virtually)
call the custom deletion function, which will call IUnknown::Release.  Thus
you can copy boost::shared_ptr's around as much as you want, and you will
not incur the overhead of calls to IUnknown::Release until the object is
finally destroyed.  The only overhead is from Boost's own reference counting
mechanism, which is much more efficient.(And with a bit of hand-crafted
assembler to do the atomic incrementing, etc. the mutex in the 1.30
implementation could be removed, making it even more so.  Or perhaps I'm
missing something?)

> It makes little sense to make the argument that on one end they are
> trivially inlineable and on the other end to say they work with COM
> which can't be trivially inlineable.

See above -- it makes perfect sense to me.  Irrespective of whether or not
you're using COM, everything other than the last destructor call (the one
that returns the reference count to zero) should be trivially inlineable.

> Small but potentially significant.

As David Abrahams asked, "have you got an application where you've measured
it to be
significant"?  I've just knocked together a test program that demonstrates
(I think) that it is not significant.  Here it is, minus #includes, etc.:

  struct X { double w,x,y,z; };
  void foo( boost::shared_ptr<X> ) {} // Doesn't get inlined

  int main() {
    timer t;  // my own timer class
    for ( int i(0), n(int(1E6)); i<n; ++i ) {
      boost::shared_ptr<X> x(new X);
      foo(x); foo(x);
    }
    std::cout << std::setprecision(3) << t.get_time() << "s\n";
  }

(Note that I've included a call to new in the inner loop.  This is justified
as a real-world inner loop is unlikely to decrement many reference counts to
zero without there being calls to new in the loop.)

I ran and compiled this on Linux as follows:

  [richard@verdi test]$ g++-3.2.1 smptr.cpp -O2 -g -osmptr \
    -I /usr/local/include/boost_1_30/
  [richard@verdi test]$ N=20; for ((i=0;$i<$N;++i)); do \
    ./smptr; done | awk '{s+=$1} END{printf("%.3f\n", s/'$N')}'
  0.961

Then I modified the boost/details/shared_count.hpp header so that instead of
virtually calling dispose and then destruct, it static casts up to the
relevant type and calls the functions non-virtually.  (I'm happy to send the
relevant diff to anyone interested.)  Compiling and running again gives a
figure of 0.914.  This represents about a 5% speed improvement and seems to
be reproduceable.  Is this really a big enough improvement to justify
loosing the ability to specify arbitrary deletion functions (needed, for
example, to support COM objects), and loosing the ability to (usefully)
instantiate the class on incomplete types?  In my opinion it is not.

Also bear in mind that the body of my inner loop was relatively trivial --
two uninlined calls to foo, with associated incrementing and decrementing of
the reference counts.  A more realistic example (e.g. one where foo does
something) will reduce the size of the improvement.

> The boost
> reference counted model seems (correct me if I am wrong) to require
> incrementing and decrementing reference counts when reference counted
> objects are trivially passed or retruned.

Don't pass by value -- pass by const reference.  In many circumstances, the
compiler is allowed to make use of various return value optimisations to
remove the copy constructor calls when objects are returned.  Together these
can help remove spurious reference increments and decrements.

> >>Of the 2 pointer passing templates one is intended
> >>to imply a policy that the receiver is being passed the responsibility
> >>to release a reference
> >
> >
> > This is what std::auto_ptr does when passed by value.
>
> I don't get what auto_ptr has to do with this discussion.  Please
elaborate.

The std::auto_ptr class was designed to allow ownership to be passed to or
returned from functions.  It does not incur the overhead of reference
counting, and I've never seen an implementation that uses any virtual
functions.  If this is what you want, you should be using auto_ptr (or
something else) for the job.  The shared_ptr class is not, and isn't
intended as, a panacea.  Use it when it is appropriate, but there will
always circumstances where better choices are available.

> > And this is what passing the raw object by const reference is for.
> > Alternatively, passing shared_ptr by const reference (or even by value)
> > would work.
>
> But passing a const pointer has nothing to do with policies regarding
> obligation for decrementing reference count.

If you don't want the function being called to do anything with the
reference count, and you know the function doesn't need to store the object,
then you don't have to pass it as a reference counted object.  Pass it as by
raw pointer, or raw const reference, or even const reference to a
shared_ptr.  None of these will cause any additional incrementing or
decrementing of the reference count.  Personally, I usually pass objects by
const reference to shared_ptr.  This gives the called function the
flexibility to retain a copy if necessary (and thus increment the reference
count), but avoids modifying the reference count if it doesn't need to.
I've usually found this to be a good solution.

> > Could you please give an example of the sort of leak you hope to avoid?
>
> Yep, when passing a reference counted pointer and the obligation to
> decrement the reference count and no decrement is done by the callee (or
> similary when returning a reference counted pointer).

Could you actually give an example (i.e. C++ code) that causes such a leak
using boost::shared_ptr?  I don't see how it is possible without willfully
abusing the pointer's interface, in which case you deserve everything you
get.

>   In
> > my experience with boost::shared_ptr, the main source of memory leaks is
> > when you have cyclic references.  After that, I think I can honestly
say, I
> > get more memory leaks due to compiler bugs than due to misusing
> > boost::shared_ptr.
> >
>
> Yes, and multiple useless calls to reference counting methods.

Well written code and a good compiler (i.e. one with good NRVO support) can
avoid a great many unnecessary increments and decrements.  And as I've said,
with a good smart_ptr implementation the reference counting functions can be
very lightweight -- they do *not* routinely result in virtual function
calls.

> In tight loops you want to avoid this and if you are limited to using
> raw pointers you diminish greatly the value of what the smart pointers
> are doing for you.

There is always a trade off between efficiency and elegance / safety /
genericity.  In a tight inner loop, you may want to consider using a
different type of pointer, such as boost::scoped_ptr.  (It's a shame this
was removed from the proposed addition to the standard, although a const
std::auto_ptr will do the same job.)  Perhaps functions called by the inner
loop should be taking the underlying object either by const reference or by
pointer.

> > Although I'm not overly familiar with COM, my understanding is that by
using
> > a suitable deletion function, a shared_ptr can be allowed to made to
hold a
> > COM object.  Persumably the game is to get the deletion function to call
> > obj->Release().
> >
> > This avoids any need to specify the functions to increment and decrement
the
> > reference counts.
>
> I don't get it OR this is just plain FUD.  You can certainly make it so
> the template has default paramater values and hence you get the benefit
> described above yet it is trivial to use where you're applying these
> templates to reference counting interfaces that pre-exist (like COM).

I didn't say it wasn't possible to add a template (with default argument),
merely that it wasn't desireable.  David Abrahams has already explained why,
although I'd perhaps add that with the current template template parameter
semantics (and yes, people do use these) an additional template parameter
with a default argument is not invisible.

> Why limit the ultility of the smart pointer classes ?

I don't believe it is.

> >>c) Reference to pointer within.
> >>
> >>There are older interfaces that expect a pointer to pointer to the
> >>object to be returned.  It would be most advantageous for a `safe' way
> >>to access the inner pointer.

[...]

> I think we're talking about different things.

Perhaps you'd care to elaborate what you're talking about, then?  Can you
give a example (including C++ code) of where you would want to extract a T**
pointer, and what semantics you would want it to have?  In particular,

  - what happens when the value of the underlying pointer (i.e. the
    T*) is changed?

  - what happens to the ownership of the pointer, and how is the
    reference count affected.

  - what is the lifetime of the the T**?

> Before I go and invest my time doing such a thing, I would prefer that
> this discussion have some interest.

It already has -- you've had replies from Peter Dimov, David Abrahams and
Howard Hinnant -- all extremely highly respected members of the C++
community.

> In simple terms, I'm saying "I have some ideas that may make sense, if
> you want me to explain it further, then show me you're interested in
> dicussing it".

I'm very interested.  If there really are issues that could be addressed,
then I would be very interested to know exactly what they are, and how they
could be addressed.

> So far, it seems like people (other than yourself) care very little.

I think people are just skeptical rather than disinterested.  If you can
produce some realistic examples (i.e. including C++ code) where the issues
you mention manifest, I think people will pay attention.

--
Richard Smith




---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: pdimov@mmltd.net (Peter Dimov)
Date: Thu, 10 Jul 2003 03:50:33 +0000 (UTC)
Raw View
brangdon@cix.co.uk (Dave Harris) wrote in message news:<memo.20030709184108.26761A@brangdon.madasafish.com>...
>
> I agree it would be nice if cases like:
>
>     {
>         boost::shared_ptr<int> p( new int );
>         func( p );
>     }
>
> could be optimised so that p's count is never more than 1.

If your 'func' above is

void func(shared_ptr<int> q);

and not

void func(shared_ptr<int> const & q);

then the optimization you want is not possible. Consider:

void func(shared_ptr<int> q)
{
  // side effect 1
  q.reset(); // potential side effect 2
  // side effect 3
}

Whether q is unique or not is detectable. The compiler is not allowed
to destroy p before func returns, regardless of the presence or
absence of use_count().

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Thu, 10 Jul 2003 21:55:20 +0000 (UTC)
Raw View
David Abrahams wrote:
> gi2nospam@mariani.ws (Gianni Mariani) writes:
>
>
>>        /**
>>         * MadeTransfer()
>>         * --------------
>>         * This method sets a member variable indicating that
>>         * ownership of the pointee has been successfully
>>         * transferred from this AT_LifeLine to some persistent
>>         * pointer.
>>         */
>>
>>        inline void MadeTransfer() const
>>        {
>>            Assert( ! m_hasBeenTransferred.m_value );
>>            m_hasBeenTransferred.m_value = true;
>>        }
>>
>
>
> Why is setting m_hasBeenTransferred to true better than incrementing
> a reference count?  It doesn't seem like you can achieve much of a
> savings here.
>

Because the true MadeTransfer() method is :

inline void MadeTransfer() const {}

It does nothing !

There are two versions of LifeLine, one for "debugging" and the regular
version.  You're looking at the debug version.



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Thu, 10 Jul 2003 21:55:25 +0000 (UTC)
Raw View
In article <3F0CAA84.4F32EA96@acm.org>, Pete Becker
<petebecker@acm.org> wrote:

| But not until I've finished random number generators. <g>

You're ahead of me on that one Pete, and good job! :-)

--
Howard Hinnant
Metrowerks

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: gi2nospam@mariani.ws (Gianni Mariani)
Date: Mon, 7 Jul 2003 02:25:24 +0000 (UTC)
Raw View
I would like to discuss :

http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1450.html

I would like to see 3 more features in the libraries regarding smart
pointers, in particular reference counting smart pointers.


a) Returning or passing smart pointers.

A number of issues occur when passing or returning pointers. The most
critical one is when in a tight loop and passing a reference counted
pointer.  The calling of a virtual method to increment and decrement the
reference count can become a significant performance issue.  Passing the
raw pointer produces confusing semantics.  The best solution is to
create 3 co-operating smart pointer classes.  Once class is intended to
be for objects that are semi-persistant, the other 2 are for passing or
returning pointers.  Of the 2 pointer passing templates one is intended
to imply a policy that the receiver is being passed the responsibility
to release a reference while the other is intended to imply that no sucj
responsibility exists.  Passing of a raw reference counted pointer would
be considered at worst an error or at best a legacy issue.

In practice, this system reduces the errors due to reference counting
leaks/bugs to virtually zero.  I have implemented such a scheme where on
of the smart pointer types (the one where the implication is to pass the
obligation to call release) is instrumented and will assert on improper
usage.


b) Interface for reference counting.

There are a number of different methods names/schemes used for managing
reference counts.  I suggest that to make the library truly useful, it
would be best to parameterize the reference counting methods through a
template argument.  This would enable the use of the smart pointer
templates with systems like MS-COM as well as inc()/dec() methods.


c) Reference to pointer within.

There are older interfaces that expect a pointer to pointer to the
object to be returned.  It would be most advantageous for a `safe' way
to access the inner pointer.


I have a working implementation of a smart pointer reference counting
library that implements all of these features.  I would be more than
happy to submit it as an alternative smart pointer system for consideration.



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]