Introduction

The subject is one of the most treated in many OOP and Generic code books, with a variety of solutions. But - in my experience - I often found myself in trouble with certain "too zealot" implementations.

Many of you probably know libraries like Boost or Loki.

Well .. if you feel fine with that implementation of smart pointers ... forget about this article! Even if it can give something new to you, you'll probably already have enough motivations to stay with them, asking why another set of wheel.

But if you think that code to be sometimes an overkill, and you need something tiny, or you also need something simpler to be expanded, or you simply want to understand something about a possible way to do a policy based design, then you can get something from here.

Since I'm not new to this kind of stuffs, and some readers may also know other articles by me on the same subject, I must warn you: this is not an update of the previous series, but restarting with a new engine, more "modern" and less oriented in conserving old habitudes like Hungarian notation or other zealot implementation of patterns.

This time I won't respect any religion, but I consider all of them, case by case, with one objective: minimize the needed code.

Code structure

Since many classes are templates, I preferred an "inclusion model": all the code is inside the headers, that you can include in your projects. Headers dependency is resolved inside the headers themselves, no "cpp" files are provided.

All function and classes are grouped into namespaces, and all namespaces are grouped in the GE_ namespace. The idea is that such symbol should not conflict with any other third party library you can eventually use. If it does, just replace the GE_ string across all my provided files.

No dependency on MFC, ATL, or WTL exists. Instead, I depend on C++ STL and some CRT headers. All those dependency are included in all the files, through the "stdx/dependency.h" file.

That file also includes some helpers headers used extensively across all others. It doesn't include the headers of the "stdx" project itself.

To avoid entanglement between "standard" language classes and Windows related code, everything not related to Windows is coded independently from "windows.h" and grouped inside the "stdx" utility project. Instead everything that is a C++ wrap around Windows functionalities will be wrapped in the "winx" utility project (not in this article).

Testing units are inside the appropriate C++ projects, named C0, C1 … Cx. Don't run them in release version outside a debugger, since they don't have any effect. They make sense in debug mode, run inside a debugger, since they trace in the output window. A step-by-step execution is also a good way to see how they work.

Used patterns

The mostly used pattern is reference counting and the most used design pattern is policy inheritance. Not because I believe these patterns are able to solve everything, but because I found them particularly useful in handling dynamically allocated and limited resources.

The code is compiled with C++ 7.0. It should also work fine with C++ 7.1 or even 8.0 (native), apart from some damn typename keyword eventually forgotten "under the keyboard".

With reference counting I essentially mean, to apply some predefined action to objects at certain "moments" of their lifecycle, like the first time they are referred, on every new reference, every time a reference is lost, and when no more reference exist.

With policy inheritance I mean, an implementation of a given class operation across functions inherited by a base that is supplied across a template parameter. Typically:

template<classbase>
class myclass: publicbase
{
...
};

Since this can be recursive, you can combine policies with declaration like:

Essentially, Derived is supposed to be a class having myclass<Derived> as its base, and that can be itself a (common) base for a variety of types overriding its interface.

Other patterns, like Composite-Visitors or like Signals-Events etc. are implemented mainly unorthodoxly, on top of these, mixing policy design with RTTI OOP inheritance (virtual functions, virtual bases etc.).

Implementing reference counting

A classical reference counting implementation is the one that comes with "smart pointers", in their various flavours like boost's or loki's.

But dealing with Win32 is a bit different: reference counting requires not only destruction policies, but also different ways to store data, (to close a HANDLE, you may need other information than the HANDLE itself) different creation and acquisition policies, the capability to handle both pointer and value semantics, the availability of strong and weak references and so on.

As far as my experience concerns, policy inheritance gives the required flexibility to handle modularly all these variants.

The std::smart<.> class

It is the class where to start from, to have smart pointer, smart handles, smart whatever. Marketing apart, it must be as empty as possible implementing only what cannot be inherited (that is: copy and assignment) in terms of inherited function from a parametric base.

Note that smart doesn't contain any data or data operations. It supposes everything is inherited. The only thing it does is to call assign that is supposed to be inherited by smart_policy in all the required variants. Types definitions are also considered as inherited. Conversion is supported from other types of smart, or from an acquire_type.

refcounts::policy<.>

Reference counting is implemented inside the stdx::refcounts namespace, by the stdx::refcounts::policy class, that handles the reference counters (for both strong and weak references) and delegates to its parametric base the storage of data and their assignments.

strong means that a reference controls the object it holds (for example, by deleting it when no "controllers" control it), weak means that a reference "observes" the held object, reporting an invalid state, for example, if no more existent, but doesn't manage the object itself.

It also provides as a public function, that – due to the inheritance in smart is made so available- the operator!() that is true if there are no smart references active on the referred counters (or ... there is no object), or if the inherited policy has its own operator!() returning true.

Similarly, operator== and operator< short-circuit the inherited functions if the referred counter object is the same: when this happens, the pointers are equal by themselves.

Practically, it converts the assign function, in increment/decrement of shared counters, and calling of other "inherited by template parameter" functions, like on_addref, on_lastrelease, etc. like in these assign variants:

pCnt is a pointer to a countersstruct. The get_couters function must be inherited through the Type parameter of the policy template class (the policy of the policy), and is what makes us able to assign a dumb pointer to a smart pointer. We'll see later how.

The internal clear_ and set_ functions are the key of the counting mechanism, by calling pCnt->increment and pCnt->decrement respectively.

Note the way get_counters is implemented: it doesn't search for any Type to counters association: just creates new ones.

In general this is not a safe operation: passing a dumb to a smart pointer defined in this way should be done only once in the life of an object.

Note also that assignment from a different type is operated via static_cast, and note the simple delete in on_delete.

These are, clearly, minimum functionalities. We can always do something better, but "how" better, depends on different cases.

RTTI objects

RTTI objects are all classes having at least one virtual function (or inheriting some). When RTTI support is enabled on a compiler, dynamic_cast can be applied to conversions between RTTI objects. But there's more: whatever pointer to an RTTI class you have, if dynamic_cast-ed to void*, it will result in the address of the most external runtime object. That is: given a diamond you can have allocated on memory, whatever pointer to a whatever base of this diamond you have, dynamic_cast<void*>(pWhateverBase) will always return the same value; which can be used as the unique identifier for the object.

The first thing we can do is, override pointers::policy with pointers::dynpolicy:

Here a statically available std::map is used to associate a void* identifying a RTTI object with its counters. get_counters creates new counters only if no counters are already mapped for a given object. This makes a smart pointer always safe whatever dumb pointer it receives. But if many objects are in the system, the map may become huge.

Essentially, all the callbacks are delegated to the referred object, where homonymous functions are expected. Class stdx::refcountable defines those functions as virtual, allocating its own counters and keeping a reference to themselves, and doing deletethis inside on_delete.

With these definitions, the above declaration can be written as stdx::statptr<double>::strong.

Value semantics

Another interesting policy family can be obtained by considering a reference counting wrapper not to store a pointer (giving a smart pointer) but for storing a "value". This is typical where you want to ally some delete or create policy, for example, to a Windows HANDLE.

But the problem isn't necessarily bound to Windows: you can do the same, for example, with an array index etc..

The namespace values, contains a policy and a mapped policy. Just like for pointers.

The key of the management is the get_buffer function: if the hold counter is greater than 1 (that is: the data is shared) the data is cloned.

Note also that no functions allow to modify the data or to access them in non-const mode, except getbuffer. This gives std::string the behaviour of a value class, even if it is internally a reference counting pointer.

The traits parameter must be a struct providing the staticlen and copy functions and the struct base, that can be used to inherit other functionalities. The default strings::traits implements them for a generic Type using linear loops, and provides base as an empty struct.

Note that, as far as it is provided, stdx::string is not an OOP string class, but only a data sharer. It implements as member functions only what is required to manage the data, but you must still rely on external standard library functions to handle string operations.

Policing example for a recurring template: compounds

Compound objects are – in their essence- objects that can collect each other, where an object may contain other objects.

This is different with a pure "generic programming" pattern, like STL, where collections contain generic homogeneous types, with each of the objects unaware of the fact that it is collected. Collecting pointers can allow polymorphism, but the objects are still unaware of the collection. With the advantage that they (or better, their pointers) can be a member of many collections, but the impossibility for an object to know about its next or previous.

Compound objects – in this implementation- are nodes having their own references for previous, next, parent, .... But they also need to have their own specific interface.

This is achieved by having node references expressed in terms of pointers to classes implementing node functionality by inheriting a node class: a class derives from node and the node contains pointers to that class. This is a game for recurring templates (see compounds.h).

Previous and next references are implemented in terms of pointer toDerived: but the "pointer" can be ... whatever object having a dereferencing operator. Which object, it is defined inside the traits class, that must provide the definitions that comes as in comp_dumb_traits (that used normal "dumb" pointers) or comp_smart_traits (that uses smart intrusive pointers).

Note that comp_xxx is not by itself a template, but contains all template members. Although this complicates a bit the coding of the policy itself, it results in a simpler usage of the policy, since there is no need to specify the type it must apply to.

The chain_node class

As illustrated above, chain_node holds the next and previous pointers. When comp_smart_traits is used, the forward pointer is strong, while the backward one's weak: this makes an element in the chain to keep-alive the subsequent and being kept-alive by the precedent.

The set_next and set_prev functions have obvious meaning, but a less obvious consequence: A->set_next(B) and B->set_prev(A) are not the same: in the first case B leaves its original chain and joins A's interposing between A and its original next; in the second case, it is A joining B chain, interposing between B's previous and B. In both the cases, the one who leaves the original chain is the parameter passed node.

The hierarchy_node class

Similarly, a hierarchy node is implemented by a siblings chain, with a parent weak pointer and the first and last children strong pointer. Of course, regular pointers are used if comp_dumb_traits is used.

Like a chain node, a hierarchy node has not only set_prev and set_next but also set_first, set_last and set_at. To navigate the hierarchy, get_xxx functions are available for both shallow (walking on siblings) and deep (walking children also) iterations.

Iterating onto compounds

Class stdx::ierator<class Compound, class iter_trs = iter_traits_chain, class traits = Compound::traits_t> has pointer semantics, and all the increment/decrement operators. The implementation of those operators is defined in terms of iter_trs supplied functions, that are provided as iter_traits_chain and iter_traits_deep. The internal iterator pointer type, is obtained from traits as traits::types<NODE>::wptr.

Samples of usage, can be found in C2.cpp.

To be as STL compliant as possible, stdx::iterator defines the STL iterator typedefs, and the xx_node classes have begin/end functions. But, since compounds are self-collections, end will always return a "null iterator". No doubt this is a difference with STL: decrementing an iterator that reached the end of a collection is legal in STL, but illegal here.

Independently on that, std::for_each works, and – in your own defined loops- you can both check an stdx::iterator with an "end" iterator, or simply check if it is null with operator!.

Graphs

With the same policing mechanism, graphs can be created with graph_node and graph_arc derived classes. When smart pointers are used, nodes sustain arcs (the arc lists use strong pointers) while arc only observes (by weak pointers) nodes. Thus, while nodes must exist by themselves (may be collected somewhere else), arcs are deleted when no nodes refer them, if not elsewhere referenced.

In this implementation, both graph_node and graph_arc are recurring templates taking as parameters the class derived from themselves and the class deriving their counterparts.

Note that these two classes only contain the functions to compose a graph (linking nodes with arcs). Algorithms cannot be here, since they depend on what the nodes and arcs represent. And that mostly depend on the arcs and nodes data, inside the derived classes. In this sense they are like stdx::string: generic programming classes, not OOP objects.

A sample of usage is in C3.cpp

Iterating through graphs

Since graph::nodes outgoing and incoming arcs pointers are stored in std::lists, it should be possible to iterate those lists by exposing their begin/end function and their iterator type.

But this is impractical, since such iterator iterates over pointers, thus, accessing the arcs will require a double de-reference (that is: operator-> doesn't work, and arc members should be accessed as (**iter).member). To avoid this annoying syntax, the helper stdx::derefiterator inherits whateverSTL compliant iterator, replacing the * and -> operators to mask the double indirection. This is used as graph::node::arc_iterator, that is also returned by begin_arcfrom, begin_arcto, end_arcfrom, end_arcto.

Routing messages through graphs

Similar to std::for_each, iterating over the arcs of a node or over their liked nodes can be done with graph::route_arcs and graph::route_nodes. They both keep a generic Data& and a starting Node& and apply a Fn(Arc*, Data&) and Fn(Node*, Data&) respectively. The Fn can be whateverfunctional object, including the one returned by std::mem_func, allowing to pass the Data& to a member function.

This mechanism can be also used as a sort of static-types event-routing network (since nodes can re-route the messages).

Conclusions

If you'd the patience to read up 'till now, compliments!. The article was probably a bit long.

This is not certainly an industrial code, but it can be useful in all those situations where you don't have to depend on large libraries to do small things. As I told in the beginning, if you already depend on those libraries use them. If you don't, or if you just want to try a different approach, consider this as a "starting point".

Share

About the Author

Born and living in Milan (Italy), I'm an engineer in electronics actually working in the ICT department of an important oil/gas & energy company as responsible for planning and engineering of ICT infrastructures.
Interested in programming since the '70s, today I still define architectures for the ICT, deploying dedicated specific client application for engineering purposes, working with C++, MFC, STL, and recently also C# and D.