Table of contents:
There are basically three types of smart pointers:
The first type of smart pointer has the advantage that it can be used with any type of allocated object. In other words, the smart pointer does not require anything from the object in question. However, it has two main problems compared to the intrusive type: Its size is that of two pointers (rather than one), and most importantly, it needs to dynamically allocate the reference counter (which is just an integer type) separately. Such an extra allocation not only slows down the creation and destruction of such smart pointers, but it also wastes memory (typically each allocation consumes 16 bytes of memory even though the reference counter itself typically only requires 4 bytes in 32-bit systems).
The second type of smart pointer is a clever variation: Rather than allocating a separate reference counting integer, what it does is that it doubly-links itself with all the other smart pointer instances which are pointing to the same object, effectively creating a doubly-linked list of smart pointers pointing to the same object. This way it has all the advantages of the first smart pointer type, but it doesn't need to allocate anything. Its disadvantage is that its size is that of three pointers.
The third type, the intrusive reference-counting smart pointer is the most efficient of all three, in all regards. "Intrusive" means that the reference counter is in the allocated object itself, rather than being allocated separately. This allows the intrusive smart pointer to have the size of a single regular pointer, as well as being almost as fast as such regular pointer for all operations (the only overhead in the smart pointer is that it increments and decrements the reference counter when it's copied, assigned and destroyed). Another advantage is that the reference counter itself does not require any additional memory overhead (besides its own size).
The problem with the intrusive smart pointer is that it requires the object to contain the reference counter. This means that it cannot be used with pre-existing objects which don't have it, nor with basic types.
The basic reason why traditional intrusive smart pointers can't be used with all possible types is that they leave the allocation of the object to the user, and simply expect the user to pass a pointer to this allocated object. Thus it's up to the user to ensure that the allocated object has the reference counter (for example by inheriting the instantiated class from a reference counter base class defined by the smart pointer library).
The smart pointer offered by this library, RefPtr
, takes a
different approach at the problem: It allocates the object itself, rather
than expecting the user to do it. This has both advantages and disadvantages:
Advantages:
RefPtr
instance is that of one single pointer,
and the reference counter requires only 4 bytes of memory (8 bytes in
64-bit systems) because it's allocated in the same memory block as the
object itself. (In practice the object and the reference counter are
both members of the same struct.) The speed of RefPtr
is
that of a typical intrusive smart pointer.
RefPtr
not only works with incomplete (ie. forward-declared)
types, but this support does not slow down copying nor assignment.
(It is possible to make traditional intrusive smart pointers support
incomplete types, but at the cost of copying and assignment of the
smart pointer objects being slightly slower.)
new
' and raw
pointers involved in the user code. This allows using almost any type
in a very similar way as you would use eg. STL containers. (In fact,
one could think of RefPtr
as being an STL container which
can contain one single, reference-counted element.)
RefPtr
has been designed with
the FSBAllocator in mind.
RefPtr
doesn't require the
object to have an enabled copy constructor (although there are limitations
to this; see below).
Disadvantages:
RefPtr
allocates the object itself, it cannot be
used in situations where, for example, some function returns a raw
pointer to a dynamically-allocated object, and then the calling code
would assign it to a smart pointer. In this case some other more
generic smart pointer has to be used.
RefPtr
cannot be used
as a base class type pointer which points to derived class type objects.
(On the other hand, this is the exact same limitation STL containers
have, so it still can be very useful.)
RefPtr
doesn't require the object to be copy-constructible,
as it passes all constructor arguments to the object constructor. However,
this comes with a limitation: Currently only up to 6 constructor arguments
can be passed like this. If more than this need to be passed, then it
can be done by either constructing the RefPtr
with a
constructed object (which requires a working copy constructor), or
adding an additional constructor to the RefPtr
source code.
(This problem will be fixed with the next C++ standard, which will have
support for variadic templates.)
RefPtr
is not thread-safe.
Add #include "RefPtr.hh"
to the source file where
RefPtr
is to be used.
To create a null RefPtr
, simply create one without any
constructor parameters:
RefPtr<SomeType> ptr;
To instantiate an object and initialize it, give the initialization value or values as contructor parameter, for example:
RefPtr<double> ptr1(5.2); RefPtr<SomeClass> ptr2(1, 2, 3); // SomeClass constructor takes 3 parameters
To instatiate an object which takes no constructor parameters at all, it can be done like this:
RefPtr<SomeClass> ptr(ptr.instantiate);
(This little trickery is necessary for RefPtr
to distinguish
between whether the user wants to create a null pointer or instantiate a
type which takes no constructor parameters. Although it might look odd,
it's completely safe, as 'instantiate
' is simply a value of
an enumerated type inside RefPtr
.)
RefPtr
supports incomplete types. The only requirement is
that the type must be complete in the context where it's constructed with
parameters (the default constructor does not require for the type to be
complete). Copying, assigning, (in)equality comparison and destroying the
RefPtr
instance do not require for the type to be complete
in that context.
RefPtr
has the following public functions (besides the
constructors, copy assignment operator, destructor and equality/inequality
operators):
operator->()
(for access like "ptr->value
")
operator*()
(for access like "*ptr
")
get()
: Get the raw pointer to the object (use with care)
isShared()
: Whether there is more than one
RefPtr
pointing to the object
operator bool()
: To check if it's a null pointer
(eg. "if(!ptr) ...
")
RefPtr
has support for custom allocators (mainly with
FSBAllocator in mind). As
mentioned earlier, using such an allocator is very easy and safe. For
example:
typedef RefPtr<SomeClass, FSBAllocator<SomeClass> > Ptr_t; Ptr_t ptr1(1, 2, 3), ptr2(4);
(As you can see, the only place where the allocator needs to be mentioned
is as the second RefPtr
template parameter. Everything else is
handled automatically, and thus the RefPtr
type can be used
exactly in the same way as with the default allocator. This makes it very
similar in usage to the STL containers.)
There are some limitations (imposed by practicality) on using a custom
allocator with RefPtr
:
operator*
and
operator->
).
(I know these requirements make RefPtr
unusable for very
exotic allocators, but the main idea is to support FSBAllocator, besides
the default standard allocator.)
This library is released under the MIT license.
Copyright (c) 2008 Juha Nieminen
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.