Previous articles introduced the C++ template metaprogramming language (MPL) and type lists. We showed how Boost.mpl helps us to write lists of interesting types, and how those techniques apply to classes that model MicroStation element types.
By writing a meta function that tests whether a type is present in a type list, we introduced a way to perform conditional inheritance. Conditional inheritance lets a class inherit from base classes that meet specified criteria. It enables either dynamic or static polymorphism.
In this article we review a polymorphic element class that implements dynamic polymorphism. That is, we have interface classes that declare pure virtual methods that the concrete element class must implement. If you're interested in reading about static polymorphism then skip to the following article.
The element class is declared and implemented in a header file ElementPolymorphicDynamic.h.
That header file includes …
Element base class
ILinear, IPoint) that provide specialisation
TypedElement specialisation class
LineStringElm) for each MicroStation element type.
Each concrete class inherits from the Element, TypedElement and one or more interface classes
The MicroStation software developers kit (SDK) delivers the MicroStation Development Library (MDL) and the MicroStationAPI. MDL is a library of C-style functions; the MicroStationAPI provides a C++ interface. If you want more information about MicroStation or its SDK, contact Bentley Systems.
The Element base class implements methods common to all MicroStation element types, such as
Element ID, element type and symbology.
It owns an EditElemHandle (from the MicroStationAPI), which we expect to be initialised with an ElementRef and DgnModelRef
when the class is constructed.
Type MSElementTypes appears frequently in these articles.
It is an enum introduced by MDL header file <mselems.h>.
class Element
{
private:
// No copy or assignment, which are disallowed in EditElemHandle
Element (Element const&);
Element& operator=(Element const&);
protected:
Bentley::Ustn::Element::EditElemHandle eeh_;
public:
// Construction
Element (MSElementTypes MetaType)
: MetaType_ (MetaType)
{
}
Element (ElementRef elRef,
DgnModelRefP modelRef,
MSElementTypes metaType)
: eeh_ (elRef, modelRef),
MetaType_ (metaType)
{
}
Element (MSElementCP el,
DgnModelRefP modelRef,
MSElementTypes metaType)
: eeh_ (el, modelRef),
MetaType_ (metaType)
{
}
Element (MSElementDescrP descr,
bool owned,
bool isUnmodified,
MSElementTypes metaType)
: eeh_ (descr, owned, isUnmodified),
MetaType_ (metaType)
{
}
virtual ~Element () {}
// Implementation common to all elements
const MSElementTypes MetaType_;
MSElementTypes MetaType () { return MetaType_; }
MSElementTypes Type ()
{
const MSElementTypes& t = IsValid ()? static_cast <MSElementTypes> (elementRef_getElemType (eeh_.GetElemRef ()))
: static_cast<MSElementTypes>(0);
return t;
}
bool IsValid () { return eeh_.IsValid (); }
// Check IsValid before calling any of the following …
MSElementDescrP GetElemDescrP () { return eeh_.GetElemDescrP (); }
MSElementP GetElementP () { return eeh_.GetElementP (); }
ElementRef GetElementRef () { return eeh_.GetElemRef (); }
ElementID GetElementID () { return mdlElement_getID (eeh_.GetElementP ()); }
DgnModelRefP GetModelRef () { return eeh_.GetModelRef (); }
std::wstring Describe ()
{
if (IsValid ())
{
using namespace Bentley::Ustn::Element;
Handler& handler = eeh_.GetHandler ();
Bentley::WString wcDescr;
enum StringLength
{
DesiredLength = 128, // See MicroStationAPI documentation
};
handler.GetDescription (eeh_, wcDescr, DesiredLength);
return std::wstring (wcDescr.c_str ());
}
//else
return std::wstring (L"Element not initialised");
}
};
The TypedElement class provides the inheritance framework.
It inherits from all the interface classes (ILinear etc.).
However, the inheritance of a set of meaningful interface methods takes place only if the
element type (elemType) enables that interface.
It the element does not match the interface type list then TypedElement
inherits an empty class, which the C++ optimiser will magically erase.
As discussed in
previous articles, the conditional inheritance is a compile-time decision.
Once an element class, such as LineStringElm, is instantiated all its interface methods
are defined.
template <typename elemType>
class TypedElement : public Element,
public elemType,
public ILinear<elemType>,
public IArea<elemType>,
public IPoint<elemType>,
public IText<elemType>
{
static const MSElementTypes MetaType_ = static_cast<MSElementTypes>(elemType::value);
public:
// Construction
TypedElement ()
: Element (MetaType_)
{
}
TypedElement (ElementRef elRef,
DgnModelRefP modelRef)
: Element (elRef, modelRef, MetaType_)
{
}
TypedElement (MSElementCP el,
DgnModelRefP modelRef)
: Element (el, modelRef, MetaType_)
{
}
TypedElement (MSElementDescrP descr,
bool owned,
bool isUnmodified)
: Element (descr, owned, isUnmodified, MetaType_)
{
}
virtual ~TypedElement () {}
// Implementation
// The following static bool functions provide run-time
// access to facts established at compile-time
// Complex elements
static bool IsComplex ()
{
using namespace LASolutions::ElementTypes;
return IsTypeInList<ComplexTypes, elemType>::value;
}
// Linear elements
static bool IsLinear ()
{
using namespace LASolutions::ElementTypes;
return IsTypeInList<LinearTypes, elemType>::value;
}
// Point elements
static bool IsPoint ()
{
using namespace LASolutions::ElementTypes;
return IsTypeInList<PointTypes, elemType>::value;
}
// Area elements
static bool HasArea ()
{
using namespace LASolutions::ElementTypes;
return IsTypeInList<AreaTypes, elemType>::value;
}
// Text and tag elements
static bool HasText ()
{
using namespace LASolutions::ElementTypes;
return IsTypeInList<TextTypes, elemType>::value;
}
};
There are a number of interface traits classes.
Each interface class focusses on a particular trait exhibited by MicroStation elements.
For example, the IPoint class defines the set of methods to be provided by
MicroStation elements that are 'point' objects.
That term includes those elements that need a single point (DPoint3d) coordinate to
determine their location in a DGN model.
For example, text elements, cell elements and zero-length lines are point objects.
Other interfaces define methods to be implemented by other element classes.
We won't discuss them all here because of their repetitive nature, at least
for the purpose of understanding the C++ technicalities of each interface.
However, no two interfaces define the same method.
For example, IPoint defines the Origin method,
but no other interface defines an Origin method.
Each interface class is a template class. There are three parts to each interface template …
A generic template. The generic template is never chosen
A template class, containing method declarations, that is specialised for each element type in the type list of that interface
An empty template class specialised for each element type that is not in the type list of that interface
The generic template is never instantiated, because the specialisations are always a better choice for the compiler.
The template specialisation that declares interface methods is matched when the element type is found
in the list of element types for that interface.
For example, the IPoint interface matches an element type in the PointTypes list.
If the element type is not in the list, then the second specialisation is instantiated.
But the unmatched specialisation is an empty class, and so it specifies no interface methods.
Generic Interface ClassHere's the unimpressive generic template class. It doesn't need a body because it's never instantiated …
template<typename elemType, typename enabler = void> struct IPoint;
Matched Interface ClassHere's the class template specialisation for an element type that is found in the element type list …
template<typename elemType>
struct IPoint
<
elemType,
typename boost::enable_if
< typename
LASolutions::ElementTypes::IsTypeInList
<
LASolutions::ElementTypes::PointTypes, elemType
>::type
>::type
>
{
// Virtual pure interface methods
virtual DPoint3d Origin () = 0;
};
Working from innermost to outermost, essential parts of that template are …
LASolutions::ElementTypes::PointTypes
elemType to test
LASolutions::ElementTypes::IsTypeInList,
which resolves to true if elemType is found in the list
boost::enable_if. This has the magical effect
of permitting the instantiation of the template if its argument resolves to true
<typename elemType> struct IPoint
The class body contains the interface methods (in this example there is only one) …
Origin function
Non-Matched Interface ClassHere's the class template specialisation for an element type that is not found in the element type list …
template<typename elemType>
struct IPoint
<
elemType,
typename boost::enable_if
< typename boost::mpl::not_
<
LASolutions::ElementTypes::IsTypeInList
<
LASolutions::ElementTypes::PointTypes, elemType
>
>::type
>::type
>
{
// No interface methods!
};
Working from innermost to outermost, essential parts of that template are …
LASolutions::ElementTypes::PointTypes
elemType to test
LASolutions::ElementTypes::IsTypeInList,
which resolves to false if elemType is not found in the list
boost::mpl::not_, which simply negates the enclosed test
boost::enable_if. This has the magical effect
of denying the instantiation of the template if its argument resolves to false
<typename elemType> struct IPoint
The empty class body contains no interface methods.
A concrete class models a MicroStation element.
For example, LineStringElm and TextElm represent a MicroStation line-string element and text element respectively.
The concrete class is the end product of a chain of inheritance that includes …
Element base class
TypedElement intermediate class
IPoint or IText
class TextElm : public TypedElement<LASolutions::ElementTypes::TextType>
{
public:
// Construction
TextElm () {}
TextElm (ElementRef elRef,
DgnModelRefP modelRef)
: TypedElement (elRef, modelRef)
{
}
TextElm (MSElementCP el,
DgnModelRefP modelRef)
: TypedElement (el, modelRef)
{
}
TextElm (MSElementDescrP descr,
bool owned,
bool isUnmodified)
: TypedElement (descr, owned, isUnmodified)
{
}
virtual ~TextElm () {}
// Implementation
// Origin() is specified by the IPoint interface
DPoint3d Origin ()
{
DPoint3d origin = { 0.0, 0.0, 0.0 };
// MDL methods not shown
return origin;
}
// Text() is specified by the IText interface
std::wstring Text ()
{
// MDL methods not shown
return std::wstring (L"Write this code!");
}
};
The header file defines several type definitions. These include smart pointer types, anticipating the use of these classes in standard C++ collection classes and their manufacture by a class factory …
typedef boost::shared_ptr<Element> ElementPtr; typedef boost::shared_ptr<TextElm> TextElmPtr; typedef boost::shared_ptr<LineStringElm> LineStringElmPtr; typedef boost::shared_ptr<EllipseElm> EllipseElmPtr; typedef boost::shared_ptr<CellHeaderElm> CellHeaderElmPtr; typedef boost::shared_ptr<LineElm> LineElmPtr;
| Index | |
| Type Lists and the MPL |
| Conditional Inheritance and the MPL |
| Polymorphic Classes for MicroStation Elements |
| Dynamic Polymorphic Element header file overview |
| Static Polymorphic Element header file overview |
| Element Factory |
| Development Tool Versions |
Post questions about MicroStation programming to the MicroStation Programming Forum.