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

Compare to C++ classes from other libraries

The essense of NoPtr is strict ownership of dynamically allocated objects (DAO). I.e., only one object (of class DynObj) in your program, at any given time, is responsible for the destruction of a given DAO. Just like for automatic variables (which are also strictly owned), references are used to pass objects around without copy; this is done by RRef and DynTmp.

There are several classes from other libraries that have related use. Some that I am currently aware of are:

std::auto_ptr

The std::auto_ptr is a very simple smart pointer, available with the STL, that has the following characteristics:
  1. Implicit move-on-copy: when it is copy-constructed or copy-assigned, it actually silently transfers the pointer it holds to the new auto_ptr, leaving itself with a null pointer; this is also known as "move semantics";
  2. When an auto_ptr is destroyed, it calls delete on the pointer it holds;
  3. When its operator-> or operator* is used, the pointer held is dereferenced;

DynObj does not support implicit move-on-copy semantics since: 1) this is too error prone; and, 2) this prevents it from being used in STL containers. However, when a DynObj goes out of scope it calls delete on the dynamically allocated object it represents. Currently, DynObj does not provide operator-> because it is a smart reference rather than a smart pointer. To be a smart pointer, a class must attempt to pass for a raw pointer in as many situations as possible. DynObj makes no such attempt since the idea of NoPtr is to move away from pointer semantics.

DynObj does several things that std::auto_ptr doesn't. For instance, you can pass a DynObj<T> to a function expecting a const reference to DynObj<const T> (it should be fairly obvious why a non-const reference is not allowed). DynObj supports explicit move semantics (moveToTmp() and giveAway() methods) which has none of the limitations of implicit move. Also, a DynObj<T> can be a data member of a class U even if the complete definition of T is not visible in the header for U. Last but not least, DynObj can be in STL containers while providing strict ownership semantics.

boost::shared_ptr and boost::weak_ptr

The fundamental difference with Boost's shared_ptr is that boost::shared_ptr provides shared ownership semantics. This means, for instance, that only the last of several shared_ptr's that own the same DAO calls delete on the DAO held, and that when a shared_ptr is copy constructed or copy-assigned (even implicitly), it makes a shallow copy of the DAO. Because there is only one DynObj that owns a given DAO, copy construction and copy assignment don't make sense and are not provided (see however the discussion on STL containers).

One feature difference: shared_ptr allows you to override how the destruction of the DAO will take place (including no destruction), which adds the ability to smarten pointers not just for DAO's but for COM objects (which are not necessarily DAO's: they may already exist on another machine), or handles etc. DynObj currently does not because there hasn't been a need and there is a non-negligible cost to that feature.

A design difference is that boost::weak_ptr is needed to prevent circular references when using shared_ptr, whereas no such class is needed for NoPtr::DynObj. A class, RRef, is provided, but based on a extra role that had to be supported, rather than a work-around against circular dependencies. Strict ownership requires you to follow the scope of the owner and greatly decreases the likelyhood of ownership inversion. For these reasons, strict ownership in the form of NoPtr provides somewhat cleaner separation of use from ownership and therefore clearer algorithms (though I must admit clarity of algorithm is much more a function of programmer than library :).

Though the mechanism used is different, both shared_ptr and DynObj allow you to pass a shared_ptr of T to a function expecting a const reference to one of const T. The different mechanism has the added advantage (for shared_ptr) that you can also pass to a function expecting non-const reference, and ref to base, but this can lead to unexpected results to the unwary user, and the disadvantage that it is more costly (due to reference counting).

Finally, NoPtr tries to use different classes for different roles: DynObj for DAO's, DynTmp for unnamed temporaries, RRef instead of references/pointers. It should be fairly rare to have to specify a DynObj in an interface, if that interface does not directly affect ownership. Most often, RRefs or raw pointers would be used instead. When shared ownership is an intrinsic part of your design, this leads to clearer semantics.

An important similarity is that, like boost::shared_ptr, NoPtr::DynObj works inside containers. Therefore, you should not have to use a shared_ptr if shared ownership isn't fundamentally part of your design.

boost::scoped_ptr

Boost's scoped_ptr is a better auto_ptr:
  1. It does not allow for copy construction/assignment;
  2. It does not require the pointer to be of a complete type.

For that reason, one could say that boost::scoped_ptr is a subset of NoPtr::DynObj. However, DynObj

  1. Can be safely used inside an STL container;
  2. Cooperates with another class, NoPtr::RRef, to allow smart references to the DAO to be used, and also collaborates with DynTmp so that a DAO can be moved to different parts of a program while preserving strict ownership.

MangoPtr

The Mango-ptr library started off with the same goal as NoPtr: provide strict ownership management for dynamically allocated objects (DAO). However, value-based containers don't support this easily so Mango-ptr eventually provided a shared-ownership smart pointer (SOSP). I found it to be rather costly to require one SOSP per element in a list, due to the extra reference counting needed, and so ended up adding owned containers, which were wrappers around value-based containers.

Owned containers were a problem because of iterators: they make it impossible to centralize operations, and so things like std::list<Foo>::remove could not be made to call the necessary destructor, without replicating the complete interface for std::list, and similarly for other STL containers, clearly not a practical solution. Also, the SOSP was only a hack for STL containers, since I really didn't want it, prefering strict ownership, plus a very good and reliable boost::shared_ptr existed if shared ownership is really what I wanted.

Finally, Mango-ptr used reference counting for the debug capabilities, a costly implementation. I decided to redo the library from scratch, going back to the drawing board with regards to the concepts I wanted to provide.

The main differences are therefore:

Loki's SmartPtr

Loki's SmartPtr is a very advanced, policy-based smart-pointer that can be adapted to almost anything you want via its policies for lifetime management, thread-synchronization, etc. It would most likely be possible to devise a set of SmartPtr-based classes that would do the same as DynObj, DynTmp and RRef, and plus would automatically be thread-safe etc.

However, I have a few reservations about Loki's SmartPtr:

Plus, it wouldn't be as much fun to just use someone else's library :)

Arglib's smart pointers

The smart pointers in arglib are described here. Probably the simplest comparison is to extend the table for the DynObj and RRef:

Class Ownership Copying Incomplete Types Const-qualified
Owned<T*> sole none yes yes
RRef<T> none value yes yes

The "const qualified" is much better in NoPtr: it is the same as for raw pointers to DAO's, something that the arglib pointers do not do. In fact, the arglib pointers all force constness of the DAO to be the same as that of the smart pointer, which is rarely a desirable feature. Instead, NoPtr allows you to specify if the proxy is const, or if the DAO is const, or both or neither.


Generated on Mon Aug 4 18:51:32 2003 for NoPtr C++ Library by doxygen 1.3.2