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 …
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.
Here'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;
Here'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
Here'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.