KeyValue
User's Manual - version: 0.3

14. Using custom smart pointers

Each builder gives to KeyValue a pointer to an object that is then, stored in KeyValue's repository. Normally, the memory occupied by any of these object is allocated on the heap (through operator new) and to prevent memory leaks, smart pointers must be used. The most popular smart pointer types are, probably, ::boost::shared_ptr and ::boost::intrusive_ptr and KeyValue provides good support for some of them. Some projetcs, however, have a genuine need for other types of smart pointers and KeyValue is flexible with respect to this point.

Obviously, KeyValue needs to know about the smart pointer type used by the bridge and core libraries. The next sections explain how to provide the required information.

14.1. The pointer traits header file

This file provides the information on the smart pointer used by bridge and core libraries. The name of this file is flexible but, for sake of concreteness, in this manual it will be called PtrTraits.h.

PtrTraits.h must be included at the very beginning of all builder and calculator source files. (For instance, every processor implemented by the bridge-example library #includes the file bridge-example/PtrTraits.h.)

In the sequel, we shall see the four tasks that PtrTraits.h must acomplish. Section 14.1.5 presents examples of pointer traits files that come with KeyValue and can be used by bridge developers as samples for writing their onw.

14.1.1. Implementing the template struct value::PtrTraits

This struct depends on a parameter ObjectType and provides a typedef, namely Type_, to the smart pointer class that points to objects of type ObjectType.

For instance, in bridge-example/PtrTraits.h we have:

namespace keyvalue {
namespace value {

template <typename ObjectType>
struct PtrTraits {
  typedef ::boost::shared_ptr<ObjectType> Type_;
};

} // namespace value
} // namespace keyvalue

This tells KeyValue that, as far as the bridge-example library is concerned, a pointer to ObjectType is a ::boost::shared_ptr<ObjectType>.

14.1.2. Implementing the specialization of value::PtrTraits for void

The general implementation of value::PtrTraits defines the smart pointer for each specific type of object. However, KeyValue's repository is a container for uniform storage, that is, stored pointers must have the same type. This situation is analogous to the classic example of polymorphism found in many C++ text books where a ::std::vector<Shape*> stores pointers to several types of shapes like Square, Circle, etc.

The specialization of value::PtrTraits for void tells KeyValue what is the type of pointer that the repository stores.

For instance, in bridge-example/PtrTraits.h we have:

namespace keyvalue {
namespace value {

template <>
struct PtrTraits<void> {
  typedef ::boost::shared_ptr< ::core::Polygon> Type_;
};

} // namespace value
} // namespace keyvalue

This means that KeyValue uniformly stores ::boost::shared_ptr<::core::Polygon>s. Hence, when a ::boost::shared_ptr<::core::Triangle> is given to KeyValue, it is cast to a ::boost::shared_ptr<::core::Polygon> before being stored. Later, when the bridge-example library asks for this object back, KeyValue does the oposite cast.

14.1.3. Implementing the function dynamic_pointer_cast

As we have seen, KeyValue casts pointers to specific types to pointers to the generic type and vice versa. The cast specific-to-generic is performed by a constructor and this point is explained in more details in Section 14.3. The generic-to-specific cast is performed by a template function which, essentially, has the following signature:

template <typename ObjectType>
value::PtrTraits<ObjectType>::Type_
dynamic_pointer_cast(const value::PtrTraits<void>::Type_& genericPointer);

The implementation must obey the semantics of the C++ built-in cast operator dynamic_cast. Specifically, if genericPointer does point to an ObjectType, then dynamic_pointer_cast returns a value::PtrTraits<ObjectType>::Type_ pointing to the same object. Otherwise, a NULL value::PtrTraits<ObjectType>::Type_ is returned.

This function is named after ::boost::dynamic_pointer_cast which satisfies the requirement describe above. Hence, for some boost smart pointers, ::boost::dynamic_pointer_cast is exactly what is needed. More precisely, PtrTraits.h should not implement dynamic_pointer_cast when the bridge library uses either ::boost::shared_ptr or ::boost::intrusive_ptr. In this case, KeyValue calls ::boost::dynamic_pointer_cast.

For example, bridge-example/PtrTraits.h sets the specific pointer to be ::boost::shared_ptr<ObjectType> and the generic pointer to be ::boost::shared_ptr<::core::Polygon>. Hence, it does not implement dynamic_pointer_cast. Notice that, in this case, the signature above reads

template <typename ObjectType>
::boost::shared_ptr<ObjectType>
dynamic_pointer_cast(const ::boost::shared_ptr<::core::Polygon>& genericPointer);

and matches the one in namespace ::boost whose implementation is #included into bridge-example/PtrTraits.h through boost/shared_ptr.hpp.

If you do need to implement dynamic_pointer_cast, then you can place it in one of the following namespaces:

  • The global namespace.

    Do not do this! It does work but is considered bad practice (namespace pollution).

  • Namespace value.

    The call to dynamic_pointer_cast comes from this namespace and then, if this function is there, then the compiler will find it.

  • The namespace that contains the smart pointer.

    For instance, when using ::boost::shared_ptr, the function can be (and it is) in namespace ::boost. Analogously, if your curstom smart pointer belongs to namespace ::foo::bar, then dynamic_pointer_cast can be placed in ::foo::bar as well.

14.1.4. Defining the macro KEYVALUE_PTR_TRAITS_FILE

Briefly, this macro can be uniformly set in this way:

#ifndef KEYVALUE_PTR_TRAITS_FILE
#define KEYVALUE_PTR_TRAITS_FILE __FILE__
#endif

Section 14.2 provides detailed information about this macro.

14.1.5. Examples of pointer traits header files

Given the popularity of boost libraries, KeyValue provides three examples of pointer traits files based on some boost smart pointers. Bridge developers can use any of them almost out of the box:

keyvalue/value/ptr-traits/shared_ptr.h

This implementation is based on ::boost::shared_ptr and can be used when all core library types managed by KeyValue derive from the same base polymorphic class. The original file must be adapted to reflect the correct base class.

Notice that bridge-example/PtrTraits.h is basically a copy of this file with the base polymorphic class set to ::boost::shared_ptr<::core::Polygon>.

keyvalue/value/ptr-traits/intrusive_ptr.h

Similarly to the previous file but based on ::boost::intrusive_ptr, you can use this file when you have a common polymorphic base class for all core library types managed by KeyValue. Here again, you must edit the file to use the correct base class. Other constraints on the base class, regarding the referencing counting, are explained in boost's documentation and are outside the scope of this manual.

keyvalue/value/ptr-traits/AnyPtr.h

This implemenation is based on util::AnyPtr. Opposite to the previous implementations, this one does not require a commom polymorphic base class and this file can be used as it is with no need for adaptations.

The implementation of util::AnyPtr is very similar to the one presented in [4].

None of the files above (or their derivatives) needs to provide the implementation of dynamic_pointer_cast. Indeed, for the first two the implementation is provided by boost and for the third one the implementation is provided by KeyValue.

14.2. The macro KEYVALUE_PTR_TRAITS_FILE

The macro KEYVALUE_PTR_TRAITS_FILE must be set to the name of the pointer traits header file. Unfortunately, there are two places where this macro is set and both definitions must agree, otherwise weird errors can occur.

The configuration file config/config.mak:

You should set KEYVALUE_PTR_TRAITS_FILE to either an absolute or a relative (with respect to keyvalue directory) path.

For instance, to use bridge-example/bridge-example/PtrTraits.h and assuming that KeyValue was unpacked in /home/cassio/keyvalue-0.3, you set

KEYVALUE_PTR_TRAITS_FILE := /home/cassio/keyvalue-0.3/bridge-example/bridge-example/PtrTraits.h

Analogously, if KeyValue was unpacked in C:\Users\cassio\Documents\keyvalue-0.3, then use

KEYVALUE_PTR_TRAITS_FILE := C:/Users/cassio/Documents/keyvalue-0.3/bridge-example/bridge-example/PtrTraits.h

Alternatively, in both cases above, you can also use

KEYVALUE_PTR_TRAITS_FILE := ../bridge-example/bridge-example/PtrTraits.h
The pointer traits header file:

This is the very same file that KEYVALUE_PTR_TRAITS_FILE points to. Therefore, the simplest way of defining this macro is using the standard predefined macro __FILE__:

#ifndef KEYVALUE_PTR_TRAITS_FILE
#define KEYVALUE_PTR_TRAITS_FILE __FILE__
#endif

14.3. Constraints on custom smart pointers

KeyValue has some expectations on the smart pointers received from builders. This section covers the conditions that smart pointer implentations must verify.

Most smart pointers are implemented as template classes parametrised on the type of object pointed to. Although this is not a KeyValue's requirement, to simplify the presentation, we will assume this is the case and we shall call this template MinimalPointer.

The interface that KeyValue requires MinimalPointer to implement is given by the skeleton below.

template <typename ObjectType>
class MinimalPointer {

public:

  MinimalPointer(const MinimalPtr& orig);

  ~MinimalPointer();

};

As we can see, MinimalPointer must have a public copy-constructor and a public destructor (virtual or not). We emphasize that this interface is the minimum requirement and smart pointers will certainly extend it. Having said that, we notice the omission of constructors (apart from the copy-constructor). Surely, there is no class without a constructor but, because KeyValue does not create these pointers (it only copies those created by the bridge), it does not require any other particular constructor to be implemented. Additionally, KeyValue does not dereference the pointer and, thus, does not require the implementation of operator ->(). The same holds for other methods usually implemented by smart pointers.

Recall that KeyValue receives pointers to different types of objects but, for uniform storage, casts them to a unique smart pointer type. For the sake of this presentation, we will assume that this smart pointer is a specialization of MinimalPointer when ObjectType is void. Its minimal interface is given below.

template <>
class MinimalPointer<void> {

public:

  MinimalPointer();

  ~MinimalPointer();

  MinimalPointer&
  operator =(const MinimalPointer& orig);

  bool
  operator ==(const MinimalPointer& rhs) const;

  template <typename ObjectType>
  MinimalPointer(const MinimalPtr<ObjectType>& orig);

};

The following public methods must be implemented: the default constructor, the destructor (virtual or not), the assignment operator, a comparison operator and a template constructor taking a generic smart pointer as argument. The first three methods do not need any further comments. Now, we shall consider how KeyValue uses the last two.

Usually smart pointers implement operator bool() returning false, if the pointer is NULL, or true, if it is not. KeyValue does not require that. Instead, it considers a pointer to be NULL if, and only if, the result of comparing the pointer (through operator ==()) with a default-constructed one gives true.

When KeyValue needs converting a MinimalPointer<ObjectType> into a MinimalPointer<void> it uses the template constructor above which can be explicit or not.



[4] Neri, C., Twisting the RTTI System for Safe Dynamic Casts of void* in C++, Dr.Dobbs, April 2011 (http://drdobbs.com/cpp/229401004).

Valid HTML 4.01 TransitionalValid CSS!