The Policy Bridge Design Pattern

The Policy Bridge Design Pattern

By Matthieu Gilson

Overload, 15(79):, June 2007


Matthieu Gilson offers some thoughts on using a policy based mechanism to build efficient classes from loosely coupled components.

The problem considered in this paper is not new and relates to generic programming: how to build a class made of several 'components' that can have various implementations where these components can communicate with each other, while keeping the design of the components independent? For instance, an object would consist of an update mechanism plus a data structure, where both belong to a family of various mechanisms and data structures (distinct mechanisms perform distinct processes, etc.; thus some couples of mechanism-data are compatible and others are not).

To be more precise, such components would be implemented in classes (or similar entities) and use methods from other 'classes', while their design is kept orthogonal. The introduction illustrates this problem and then presents a solution using member function templates.

An alternative solution (so-called Policy Bridge) is then presented, where the 'components' are implemented in policies [ Alexandrescu01 ], [ Vandevoorde/Josuttis03 ] and the pattern 'bridges' them to construct an instantiated type. The term 'bridge' here refers to the usual Bridge pattern: the instantiated type can be seen as an interface that inherits the functionality of all the policies it is made of. The compatibility between the policies (for instance checking the interface at a call of a function from another policy) is checked at compile time. A common interface can be implemented with the cost of a virtual function for each call.

So far, the functionality achieved by the policy bridge is similar to the ones using member function templates and a brief comparison with meta-functions is explored as well. The code may be heavier but in a sense the design is more modular with policies. A combination of policies with meta-functions, to bring more genericity to encode the components, is then introduced. Policy-based visitors that enable visiting of some of the policies involved in the construction of an object via its interface.

A generic version of the policy bridge is then presented and the automation of such a code for an arbitrary number of policies (using macros from the preprocessor library of Boost [ Boost ]).

The example code runs under g++ (3.4.4 and 4.02), Visual C++ (7.1 and 8), and Comeau.

Introduction

Let us play with neurons. Such objects can be abstracted mathematically, with various features and mechanisms to determine their activity: activation mechanism (their internal update method); synapses to connect them; etc. In order to simulate a network made of various types of neurons interconnected with various types of connections, and keep the design as flexible as possible to be able to combine the different mechanisms together at will.

Without even going as far as connecting neurons, a fair number of design issues arise. Say we have a data structure which represents a feature of one type of neuron:

class Data1
{
public:
Data1(double a) : a_(a) {}
const double & a() {return a_;}
protected:
const double a_;
};

and an update mechanism to model the state of our neuron where the update method uses the parameter a implemented in the data:

class UpdateMech1
{
public:
const double & state() const {return state_;}
UpdateMech1() : state_(0.) {}
void update(double a) {state_ += a;}
protected:
double state_;
};

Our neuron is somehow the 'merging' of both classes, and another update method at the level of Neuron1 has to be declared so as to link the parameter of UpdateMech1::update with Data1::a() :

class Neuron1
: public Data1
, public UpdateMech1
{
public:
Neuron1(double a) : Data1(a) {}
void update() {UpdateMech1::update(Data1::a());}
};

To make it more generic for various data and mechanisms (say we have Data1 and Data2 , UpdateMech1 and UpdateMech2 ), we can templatise the features and mechanisms:

template <class Data, class UpdateMech>
class Neuron
: public Data
, public UpdateMech
{
...
void update() {UpdateMech::update(Data::a());}
};

Yet, all of the DataX would now require a method a and all the UpdateMechX would require an update method with a double as parameter, which is not flexible. The alternative using abstract base classes for both the family of the data classes ( DataBase ) and the family of the update mechanisms ( UpdateMechBase ), while Neuron would hold one member variable for each family, would face similar limitations:

class Neuron
{
...
void update() {u_.update(d_.a());}
DataBase * d_;
UpdateMechBase * u_;
};

Indeed, the interface DataBase should have a pure virtual method a :

struct DataBase
{
virtual const double & a() = 0;
};

and this would cause the interface to be rigid (even more than with the template version): it could be necessary for other update mechanisms to get a non-constant a , which does not fit this method a ; if some more variables are needed by only a particular UpdateMechX , the whole common interface still has to be modified; etc. Actually, the family of the data classes needs no common interface here, provided that for each combination with a specific UpdateMechX , the latter knows how to call the variable it needs. On the contrary, the machinery implemented in DataX and UpdateMechX and their way to communicate with each other should remain as unconstrained as possible (in terms of constructors, etc.), only Neuron would need a common interface in the end.

Speed could be another reason to discard this alternative choice, since the template version does not require virtual functions, and thus the internal mechanisms remain fast (only function calls).

The former version of Neuron can be modified to make the combinations between the DataX and the UpdateMechX more flexible. An option is to change UpdateMechX into class templates where DataX is the template argument of UpdateMechX , and to derive them from the classes DataX , as shown in Listing 1.

 class Data1
{
...
const & double a();
};
 template <class Data>
class UpdateMech1
: public Data
{
...
void update() {state_ += Data::a();}
};
 template <class UpdateMech>
class Neuron
: public UpdateMech
{ ... };
 Neuron<UpdateMech1<Data1> > nrn1;
nrn1.update();
Listing 1

This way, we can define various update mechanisms and various data classes, and combine them together. The Data family and the UpdateMech family are kept somehow independent in terms of design: basically, UpdateMech1 only requires to know the name of the method a implemented in all of the DataX to be combined with UpdateMech1 ; type checks require that a returns a type convertible into const double .

The construction Neuron<UpdateMech<Data> > still has quite strong limitations, in the sense that Data and UpdateMech are no longer on the same level in the derivation tree above Neuron . Imagine that one of the data classes also needs to use a method that belongs to the family of the UpdateMechX . Or imagine there are not just one update mechanism but two (e.g. a learning mechanism PlasticityMech in addition to the UpdateMech for some specific neurons), and they both would have to use something from the Data class family; both would derive from Data , and Neuron would derive from both, so the derivation from Data must be virtual, as shown in Listing 2.

class Data1;
template <class Data>
class UpdateMech1
: virtual public Data
{ ... };
template <class Data>
class PlasticityMech1
: virtual public Data
{ ... };
template <class UpdateMech, class PlasticityMech>
class Neuron
: public UpdateMech
, public PlasticityMech
{ ... };
Listing 2

Since there was no virtual machinery before, this may imply costs. But even if we would not care, there is a last reason to seek for another solution: there may eventually be a need to implement mechanisms with 'new' interactions that we cannot really predict at this stage; and any further change would possibly mess up our beautiful (but rigid) derivation tree.

Let us go back to thinking of our design and what we wanted of it. The key idea here is that UpdateMech1 requires that Neuron has a method a that is used by UpdateMech1 , but the fact that Neuron inherits a from Data1 does not matter to UpdateMech1 . Other mechanisms may have 'requirements', which makes mechanisms compatible or not (to be detected at compile time). They would need to be all at the 'same level' in the derivation tree, i.e. any mechanism or feature is equally likely to need something in another one. In other words, we want a design likely to combine any compatible update and data 'classes' in a flexible way and thus define a neuron out of them; these mechanisms can communicate together while they are kept as 'orthogonal' as possible (they only know the name of the methods belonging to other mechanisms they need to call); and as few virtual functions as possible should be involved in the internal machinery (within Neuron ), to try to keep fast computation.

One solution can be to use member function templates (in the classes that define the features or mechanisms) for the methods that require a feature from outside their scope (Listing 3) and to call update_impl from the class Neuron with *this as an argument (Listing 4).

class UpdateMech1
{
...
protected:
template <class TypeImpl>
void update_impl(TypeImpl & obj) {state_ += obj.a_impl();}
};
class Data1
{
friend class UpdateMech1;
protected:
const double & a_impl();
...
};
Listing 3


template <class Data, class UpdateMech>
class Neuron
: public Data
, public UpdateMech
{
...
void update() {UpdateMech::update_impl(*this)}
};
Listing 4

Note that UpdateMech1 has to be a friend of Data1 if a_impl is protected. We would need to add similar declarations for any other mechanism to be combined with Data1 , or define all the methods as public in the features and mechanisms. Another slight drawback is that the calls to the member function templates must come from Neuron , which means that having a pointer to one of the mechanisms (such as UpdateMech ) is not sufficient to call the update method if it is a template method (we still would need to pass the Neuron object as an argument).

In the end, this solution is valid and would be suitable for our requirements. Yet, we will now consider an alternative design, which brings further possibilities. The trade-off is mainly the increase in complexity in the design.

Details of the design

The purpose is still to allow any mechanism to call 'directly' any method from another 'mechanism' from which Neuron derives (via the class Neuron ). To give a rough idea, instead of the function template nested in the mechanism class ( UpdateMech1::update_impl<class TypeImpl>(TypeImpl &) ), the whole class UpdateMech1 is now a class template with a non template function ( UpdateMech1<class TypeImpl>::update_impl() ). This idea has something to do with the well-known trick used together with curiously recurring pattern (CRTP [ Alexandrescu01 ], [ Vandevoorde/Josuttis03 ]):

template <class TypeImpl>
class UpdateMech
{
void update_impl()
{
state_ += static_cast<TypeImpl &>(*this).a();
}
};

Defined this way, UpdateMech can be seen as a policy [ Alexandrescu01 ], [ Vandevoorde/Josuttis03 ] on the implemented type TypeImpl with the template argument Neuron . It allows us to call the method a that Neuron inherits from Data by means of static_cast , which converts the self-reference *this to the instantiated policy object back to Neuron .

This use of policies is somewhat different from [ Alexandrescu01 ]: a usual example is the pointer class SmartPtr defined on a type T (to make it shorter than TypeImpl ) and some properties of the pointers can be implemented by policies. For instance, let us build a policy to count the references of the pointed element (Listing 5) and the smart pointer class template is then as shown in Listing 6.

 template <class T>
class CountRef
{
private:
int * count_;
protected:
void CountPolicy() {count_ = new int();
*count_ = 1;}
bool release()
{
if (!--*count_) {
delete count_; count_ = NULL; return true;
} else return false;
}
T clone(T & t) {++*count_; return t;}
...
};
Listing 5


template <class T
, template <class> CountPolicy>
class SmartPtr
: public CountPolicy<T>
{
public:
SmartPtr(T * ptr) : CountPolicy<T>(),
ptr_(ptr) {}
~SmartPtr()
{
if (CountPolicy<T>::release())
delete ptr_;
}
...
protected:
T * ptr_;
};
Listing 6

The policy CountRef here is a mechanism that uses the type T , and SmartPtr uses T and CountRef , so there is no recursion in the design. However, we want to build Neuron which uses DataPolicy<Neuron> and UpdatePolicy<Neuron> .

First, we rewrite the mechanisms Data1 and UpdateMech1 as policies (see Listing 7).

template <class TypeImpl>
class Data1
{
protected:
typedef double VarTypeInit;
Data1(VarTypeInit a) : a_(a) {}
const double & a_impl() {return a_;}
private:
const double a_;
};
template <class TypeImpl>
class UpdateMech1
{
public:
const double & state() const {return state_;}
protected:
UpdateMech1() : state_(0.) {}
void update_impl()
{
state_ +=
static_cast<TypeImpl &>(*this).a_impl();
}
private:
double state_;
};
Listing 7

Note that the typdedef VarTypeInit was added in Data1 to define the type of the parameter required to initialise this policy.

Then the Neuron pattern straightforwardly combines these two policies, i.e. it derives from the two policies applied on itself (see Listing 8).

template <template <class> class DataPolicy
, template <class> class UpdatePolicy>
class Neuron
: public DataPolicy<Neuron<DataPolicy,
UpdatePolicy> >
, public UpdatePolicy<Neuron<DataPolicy,
UpdatePolicy> >
{
friend class DataPolicy<Neuron>;
friend class UpdatePolicy<Neuron>;

public:
Neuron(DataPolicy::VarTypeInit a)
: DataPolicy<Neuron>(a)
, UpdatePolicy<Neuron>()
{}
const double & a() {
return DataPolicy<Neuron>::a_impl();}
void update()
{UpdatePolicy<Neuron>::update_impl();}
};
Listing 8

The requirements for the policy any DataPolicyX is to define a type VarTypeInit to be used in its constructor and have a method a_impl that returns a type that can be converted to const double & . Likewise for UpdatePolicyX which must have a method update_impl (no return type is required).

Now, we can simply define Neuron1 as a combination of Data1 and UpdateMech1 :

typedef Neuron<Data1, UpdateMech1> Neuron1;

The policies can communicate together, via the casting back to TypeImpl . Indeed, thanks to the friend declarations in Neuron , each policy can access to the members of Neuron , which inherits the protected members of all the policies. Note also that the public function members of the policies and of Neuron will be public for TypeImpl . What is private within a policy cannot be accessed by other policies, to keep safe variables (e.g. via protected accessors). An alternative option is to use protected derivation instead of the public one here in the design of Neuron , in order to prevent public members of the policies from being accessible from TypeImpl .

Now, we define two new policies Data2 and UpdateMech2 (respectively in the same family as Data1 and UpdateMech1 ), but the data a_ is no longer constant in Data2 , and the update method of UpdateMech2 changes its value. Furthermore, Data2 has a learning method which modifies its variable a_ according to the variable state_ (see Listing 9).

template <class TypeImpl>
class Data2
{
protected:
typedef double VarTypeInit;
Data2(VarTypeInit a) : a_(a) {}
double & a_impl() {return a_;}
void learning()
{
if (static_cast<TypeImpl &>
(*this).state_impl()>10) --a_;
}
private:
double a_;
};
template <class TypeImpl>
class UpdateMech2
{
public:
const double & state() const {return state_;}
protected:
UpdateMech2() : state_(0.) {}
void update_impl()
{
state_ +=
++static_cast<TypeImpl &>(*this).a_impl();
}
private:
double state_;
};
Listing 9

These two policies are 'compatible' in the sense that the update policy modifies the data a_ , which is not constant as a return type of a_impl in Data2 and the type Neuron2 defined by:

typedef Neuron<Data2, UpdateMech2> Neuron2;
Neuron2 nrn2;
nrn2.update();

is thus 'legal'. So is the combination between Data2 and UpdateMech1 .

However, the class Neuron3 defined here:

typedef Neuron<Data1, UpdateMech2> Neuron3;
Neuron3 nrn3;
nrn3.update();

is ill-defined because the implemented update_impl method of UpdateMech2 tries to modify the data a_ of Data1 which is constant, and the method cannot be called on an object of this type. Such an incompatibility is detected at compile time (the member function templates described achieve same functionalities). Depending on the compiler, the incompatibility is detected when defining the type Neuron<Data1, UpdateMech2> (g++) or when calling the method update (VC8).

The term bridged objects here refers to instantiated types using a policy bridge. If we want to design an interface for all the neurons to access a_impl (with const double & as common return type) and update_impl , we can add another derivation to Neuron with pure virtual methods. For example for update_impl :

struct InterfaceBase
{
virtual void update() = 0;
};

which provides the common method update that has to be defined in a derived class. We can either do it in Neuron (or in a derived class if needed). Defining it in Neuron saves some code:

template <...> class Neuron : public InterfaceBase,
...;
void Neuron<...>::update() {
UpdatePolicy<Neuron>::update_impl();}

but then the order of the policies in the list of the template arguments is then fixed. On the contrary, in a dedicated derivation (instead of a typedef like Neuron1 above), the order of the template argument list can be changed at will:

class Neuron4
: public Neuron<Data1, UpdateMech1>
, public InterfaceBase;

and we can also imagine combining more than two policies to define neurons. The latter option would keep the design more generic (see the generic version PolicyBridgeN of Neuron ), with no member function but the constructor. Yet, the same code would be duplicated in all the derived classes. Note that such an interface also allows us to store neurons in standard containers.

Now, to implement a special interface for the 'learning' neurons, we just have to derive the suitable policy (only Data2 here, but we can imagine more) from another abstract base class with a pure virtual method called learning :

struct Interface1
{
Interface1() {list_learning_nrn.push_back(this);}
virtual void learning() = 0;
std::list<Interface1 * const>
list_learning_neurons;
};
template <class TypeImpl>
class Data2 : public Interface1 {...};

Thus, learning neurons can be handled and updated specifically (for their learning mechanisms only) separately from the rest of the neurons (useful when the distinct processes happen at distinct times for example):

for(std::list<Interface1 * const>::iterator i = 
Interface1::list_learning_neurons.begin();
i != Interface1::list_learning_neurons.end();
++i)
(*i)->learning();

This polymorphic feature only uses the derivation from an abstract base class and the cost is the one of a virtual function call at run time. Note that any bridged class (whatever the bridge like Neuron ) can actually share the same interface, and thus can be handled together as shown here. An alternative solution could be to use the variant pattern and suitable visitors [ Boost ], [ Tiny ].

Further considerations

So far, apart from the dedicated interfaces, this design based on policies has functionality comparable with member function templates. Another difference is that the body of Neuron does not have to be changed for a new mechanism with a method that requires internal communication (like when adding Data2::learning ). We indeed do not need to change Neuron with policy, whereas we would have to define in the body of Neuron a call for Data2::learning<TypeImpl> when using member function templates.

Note that the computation speed is equivalent for the two options, since all the internal mechanisms are based on function calls (no virtual function, only the use of an interface involves a virtual function call).

Further refinement can be obtained using meta-functions (that implement polymorphism at compile-time). So far, we only considered policies with one sole template argument, which is eventually the type of the bridged object ( TypeImpl ). Say we want to parametrise some of the policies with more template parameters. For example, the learning in data class Data2 would depend on an algorithm embedded in a class, thus we need to add an extra template parameter for Data2 (and not Data1 ):

template<class TypeImpl, class Algo> class Data2;    

We can no longer use the policy bridge to build neurons with distinct algorithms because the template signature of Data2 is different now (one template parameter in the original design of the policy bridge Neuron ). Meta-functions can help here:

template<class Algo>
struct GetData2
{
template <class TypeImpl>
struct apply
{
typedef Data1<TypeImpl, Algo> Type;
};
};

With this design, TypeImpl will be provided by the bridge, and all the other template parameters can be set via GetData2 (with suitable defaulting if needed). The policy bridge then becomes as shown in Listing 10.

template <class GetData, class GetUpdateMech>
class Neuron
: public GetData::template
apply<Neuron<GetData, GetUpdateMech> >::Type
, public GetUpdateMech::template
apply<Neuron<GetData, GetUpdateMech> >::Type
{
typedef typename GetData::
template apply<Neuron>::Type InstData;
typedef typename GetUpdateMech
::template apply<Neuron>::Type
InstUpdateMech ;

friend InstData;
friend InstUpdateMech;

protected:
Neuron(InstData::VarTypeInit
var_init)
: InstData(var_init)
, InstUpdateMech()
{}
};
Listing 10

Note that the instantiated policy GetData::template apply<Neuron>::Type was renamed into InstData using typedef (likewise for InstUpdateMech ) in order to simplify the code. A neuron type can thus be simply created:

typedef Neuron<GetData2<Algo1>, 
GetUpdateMech2> Neuron5;

Non-template classes with member function templates can be used with usual visitors because they have a plain type. We adapt here the processing using basic visitors [ Alexandrescu01 ] (with an abstract base class VisitorBase ) to be able to visit the policies which a bridged object is made of:

class VisitorBase
{
virtual ~VisitorBase() {}
};
template <class T>
class Visitor
{
virtual void visit(T &) = 0;
};

In particular, such a visitor must have the same behaviour for all the types T = Policy1<...> , for a given Policy1 . We thus need a tag to identify each policy, which is common to all the instantiated types related to the same policy, and a solution is to use Policy<void> because it will never be used in any bridged object. A particular visitor for Data1 and Data2 would be implemented:

class DataVisitor
: public VisitorBase
, public Visitor<Data1<void> >
, public Visitor<Data2<void, void> >
{
void visit(Data1<void> & d) {...}
visit(Data2<void, void> & d) {...}
};

Now, a method apply_vis(VisitorBase & vis) has to be defined at the interface level (pure virtual method), and defined at the same level as update to call the method in the policy (here in Neuron1 , likewise for Neuron2 ):

void Neuron1::apply_vis(VisitorBase & vis)
{
InstData::apply_impl(vis);
}

Finally, the 'visitable part' of the policy Data1 (the data that the visitor wants to access, a_ here) has to be moved in the specialised instantiation Data1<void> from which derive all the Data1<TypeImpl> . The function apply_impl has also to be defined in its body (here with a solution using dynamic_cast to check the compatibility of the visitor), as shown in Listing 11.

template <class TypeImpl> class Data1;    
template <>
class Data1<void>
{
friend class DataVisitor;
protected:
Data1(double a) : a_(a) {}
const double & a_impl() const {return a_;}
void apply_vis_impl(VisitorBase & vis)
{
Visitor<Data1<void> > * pv =
dynamic_cast<Visitor<Data1<void> > *>(& vis);
if (pv) pv->visit(*this);
}
private:
const double a_;
};
template <class TypeImpl>
class Data1
: public Data1<void>
{
protected:
typedef double TypeInit;
Data1(TypeInit a = 0.) : Data1<void>(a) {}
};
Listing 11

This way, the argument of the call of visit is the parent object of type Data1<void> instead of the object itself. The cost of the passage of the visitor is thus a dynamic_cast plus a virtual function call. In the case the visitor is not compatible, nothing is done here, but an error could be thrown or returned by the method apply_impl . Likewise with Data2<void, void> for the visitable part of Data2 .

Policy Bridge design for an arbitrary number of policies

In the end, the code can be abstracted to bridge N policies (the dedicated methods such as update , etc. are 'decentralised' in derived classes), in Listing 12 with N = 15.

template <class GetPolicy0
, ...
, class GetPolicy14>
class PolicyBridge15
: public GetPolicy0::template
apply<PolicyBridge15<GetPolicy0, ...,
GetPolicy14> >::Type
, ...
, public GetPolicy14::template
apply<PolicyBridge15<GetPolicy0, ...,
GetPolicy14> >::Type
{
protected:
typedef GetPolicy0::template
apply<PolicyBridge15>::Type
InstPolicy0;
...
typedef GetPolicy14::
template apply<PolicyBridge15>::
Type InstPolicy14;
  friend InstPolicy0;
...
friend InstPolicy14;
  typedef typename InstPolicy0::TypeInit TypeInit0;
...
typedef typename InstPolicy14::
TypeInit TypeInit14;
  PolicyBridge15(TypeInit0 var_init0
, ...
, TypeInit14 var_init14)
: InstPolicy0(var_init0)
, ...
, InstPolicy14(var_init14)
{}
};
Listing 12

This design requires each policy to define a type TypeInit , which is set to a 'fake void' type NullType when no initialisation variable is required (same NullType as used for TypeList in [ Alexandrescu01 ]). The overriding of the pure virtual functions defined in the interface are left to the implementation of the instantiated types here. A class Neuron1 is derived from a suitable instantiation as shown in Listing 13.

class Neuron1
: public PolicyBridge2<GetData1, GetUpdateMech1>
, public InterfaceBase
{
public:
Neuron1(TypeInit0 a)
: PolicyBridge2<GetData1, GetUpdateMech1>(
a, NullType()) {}
void update()
{
GetUpdateMech1::apply<PolicyBridge2<GetData1,
GetUpdateMech1>
>::Type::update_impl();
}
const double & a() { ... }
void & apply_vis(VisitorBase & vis) { ... }
};
Listing 13

A dedicated version of PolicyBridge2 could be written to create neurons with the common interface hard-wired: InterfaceBase should be put in the template argument list and the methods update , a and apply can be centralised in the PolicyBridge2bis code (to save some code lines), shown in Listing 14.

template <...>
PolicyBridge2bis<InterfaceBase, GetData, GetUpdateMech>
{
void update()
{
GetUpdateMech::template <...>::
Type::update_impl();
}
...
};
Listing 14

The code of PolicyBridgeN can be generated for all values of N in a define range using preprocessor macros such as REPEAT (cf. boost::preprocessor [ Boost ], the TTL library [ Tiny ]), i.e. to create the class templates PolicyBridge1 up to PolicyBridgeN , where N is an arbitrary limit.

See http://www.bionicear.org/people/gilsonm/prog.html for details on this implementation.

Conclusion

The policy bridge pattern aims to build classes that inherit 'properties' or functionalities from a certain number of policies (variable and function members from the protected 'space'). Each policy can call members from other ones and the compatibility between the policies is checked at compile time. This approach is modular and flexible, and keeps the design of policies related to distinct functionalities somehow 'orthogonal'.

A common interface can be designed in order to provide a main 'gate' (to all the common functionalities that have to be public, and the cost is the one of a virtual-function call), to store them in standard containers, etc. Moreover, specific interfaces can similarly be design for particular functionalities belonging to a restrained number of mechanisms, allowing us to pilot the bridged objects according to what they are actually made of. Usual visitors can be adapted and applied to these objects in order to interact with some of the policies involved in the design. Meta-functions can help and bring further refinement to use policies with more than one template parameter.

Such design proved to be useful and efficient in a simulation program where objects of various types interact: for instance neurons with distinct intern mechanisms, as well as diverse synapses to connect them; these objects are created at the beginning of the program execution and they evolve and interact altogether during the simulation. Requirements such as 'fast' object creation or deletion have not been considered here so far, 'optimisation' was sought for only for the function calls (call via the common interface class). Visitors were used for adequate object construction and linking objects from distinct kind (in particular between synapses and neurons to check compatibility when creating a synaptic connection), but not used during the execution: the interface then provides a faster way to access the functionalities of the objects.

Code is available online at http://www.bionicear.org/people/gilsonm/index.html to illustrate how such a design can be used.

Acknowledgements

The author thanks Nicolas di Cesare for earlier discussion about design pattern, and Alan Griffiths, Phil Bass and Roger Orr for very helpful comments that brought significant improvements of the text during the review process. MG is funded by a scholarship from the NICTA Victorian Research Lab ( www.nicta.com.au ).

References

[ Alexandrescu01 ] A. Alexandrescu. Modern C++ design: generic programming and design patterns applied , Addison-Wesley, Boston, MA: 001. Includes bibliographical references and index.

[ Vandevoorde/Josuttis03 ] David Vandevoorde, Nicolai M. Josuttis C++ templates : the complete guide , Addison Wesley, 2003.

[ Boost ] Boost c++ libraries. http://www.boost.org/

[ Tiny ] Tiny Template Library. http://tinytl.sourceforge.net/






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.