ADTF  3.14.3
The ucom_cast<> in depth explanation
Note
Starting from ADTF 3.2 there exists a new adtf::ucom::catwo::object template that serverly facilitates the handling of IObject interfaces. Please consider using this class instead of the more complicated adtf::ucom::ant::default_object and adtf::ucom::ant::extend_object templates.
If you want to skip the detailed explanation and just need the basic facts, you can go straight to the Summary.

Introduction to the "ucom_cast<>"

The ucom_cast<> provides a method to query an object of type IObject for interfaces it might expose. In short this means, the ucom_cast<>

  1. is an alternative for dynamic_cast<>, offering much more flexibility even without Runtime Type Information (RTTI)
  2. might be used to query objects for interfaces which are implemented using multiple inheritance
  3. might be used to query objects for interfaces which offer further interfaces by aggregation.

ucom_cast<> can only be applied to to objects of type IObject. Using it with other types will lead to a compiler error during the build process. The following rules apply:

  • Inheriting from IObject enables the inherited type to be usable with ucom_cast<>
  • Using ADTF_IID() in an interface makes the implementing interface accessible to the ucom_cast<>
  • The actual casting is performed as part of the user defined implementation, more specifically, as part of the IObject::GetInterface() methods
  • The ucom_cast<> itself wraps the casting process in a conveniently usable function template, which is used analogously to the standardized C++-style dynamic_cast<> operator

The C++ International Standard, section 5.2.7 [expr.dynamic.cast], defines the dynamic_cast<> operator as follows:

  • The result of the function template ucom_cast<T>(v) is the result of converting the expression v to type T. T shall be a pointer to a complete class type. The ucom_cast<> function template shall not cast away constness.

To use the ucom_cast<> functionality, somewhere the following header file has to be included.

//for example in your "stdafx.h" file
#include <adtf_ucom3.h>

The following chapters provide a detailed explanation of the feature set of the ucom_cast<> and how it is being applied to user defined types (UDT). The code snippets provided can be found in the example delivered in $ADTF_INSTALLATION_DIR$/src/examples/src/adtf/apps/demo_ucom_cast_app.

Implementing interfaces applicable for the ucom_cast<>

Prerequisites for the ucom_cast<>

Let's start with a first glimpse at our basic example:

class IBuilding : public adtf::ucom::IObject
{
public:
//Implementing ADTF_IID() for a type enables casting with ucom_cast<>
ADTF_IID(IBuilding, "building.adtf.example.iid");
virtual ~IBuilding() = default;
public: //pure virtual functions
virtual const char* GetLocation() const = 0; //every completed building has a location
};
#define ADTF_IID(_interface, _striid)
Common macro to enable correct treatment of interface classes by the adtf::ucom::ucom_cast<>
Definition: adtf_iid.h:17
Base class for every interface type within the uCOM.
Definition: object_intf.h:31

We have an abstract building of type IBuilding which is derived from IObject. Deriving from IObject directly enables the inheriting class to be used with ucom_cast<>. By using the macro ADTF_IID() makes objects of type IBuilding to be applicable for the ucom_cast<>. ADTF_IID() always takes the same parameters which are the type of the interface ADTF_IID() is defined in (in this case IBuilding) along with the corresponding interface id (IID) which, in turn, is used to identify the interface type.

Things to remember
  • ucom_cast<> can only be applied to objects of type IObject
  • ADTF_IID() must be defined as part of the public interface to be applicable for the ucom_cast<>

In the next step we specialize the building to be a store. The interface for this store looks as follows:

class IStore : public IBuilding
{
public:
ADTF_IID(IStore, "store.adtf.example.iid");
virtual ~IStore() = default;
public: //pure virtual functions
virtual bool Offers(const char* i_strProductName) const = 0; //get products by name
};

You may notice, that we do not expose the interface IStore to be accessible to ucom_cast<> as we do not implement the ADTF_IID() macro for this interface type. However, what we do want to expose is the interface of a special store named ICarDealer:

class ICarDealer : public IStore
{
public:
ADTF_IID(ICarDealer, "cardealer.adtf.example.iid");
virtual ~ICarDealer() = default;
public: //pure virtual functions
virtual bool Add(IVehicle* i_pVehicle) = 0; //add a car to the sale
virtual IVehicle* Sell(const char* i_str, float f32Money) = 0; //sell product against cash
virtual const IVehicle* Rent(const char* i_str) const = 0; //rent is for free
};
Things to remember
  • In an inheritance hierarchy, various interface types may or may not be made accessible to the ucom_cast<>

Eventually, we have a class cCarDealer that implements almost all interfaces by deriving from ICarDealer. The IObject::Destroy() method is implemented empty as operator delete can be used to destroy the objects. The only IObject methods not implemented are the two IObject::GetInterface() variants. The following examples demonstrate multiple ways of implementing these two methods as well as their respective (dis-)advantages. But first, here is the declaration cCarDealer.

class cCarDealer : public ICarDealer
{
public:
cCarDealer();
virtual ~cCarDealer();
public: //implementing the interfaces
virtual bool Offers(const char* i_strProductName) const;
virtual bool Add(IVehicle* i_pVehicle);
virtual IVehicle* Sell(const char* i_strProductName, float f32Money);
virtual const IVehicle* Rent(const char* i_strProductName) const;
private: //implement separately and use a default implementation
virtual void Destroy();
private: //member section
std::array<IVehicle*, 4> m_arrVehicles; // Car dealer can host max 4 cars
size_t m_nVehicles; // Current number of cars in stock
};

The implementation of the cCarDealer looks like this:

//file car_dealer.cpp
#include "stdafx.h"
#include "car.h"
#include "car_dealer.h"
cCarDealer::cCarDealer()
: m_arrVehicles(), //in the beginning the store is empty
m_nVehicles(0) //as I said...
{
m_arrVehicles = { nullptr, nullptr, nullptr, nullptr };
}
cCarDealer::~cCarDealer()
{ //car dealer needs to destroy all cars which were not sold
std::for_each(m_arrVehicles.begin(), m_arrVehicles.end(), [] (IVehicle* p) { delete p; });
}
bool cCarDealer::Offers(const char* i_strProductName) const
{
using namespace adtf_util; //cStringUtil
//the name of the offered car is the product we are looking for
auto result = std::find_if(m_arrVehicles.cbegin(),
m_arrVehicles.cend(),
[i_strProductName] (const IVehicle* pVehicle)
{
return cStringUtil::IsEqual(pVehicle->Name(), i_strProductName);
});
return m_arrVehicles.cend() != result;
}
bool cCarDealer::Add(IVehicle* i_pVehicle)
{
//insert the new vehicle at the first empty position
auto it = std::find(m_arrVehicles.begin(), m_arrVehicles.end(), nullptr);
if(m_arrVehicles.end() != it)
{
*it = i_pVehicle;
++m_nVehicles;
}
else
{
return false;
}
return true;
}
IVehicle* cCarDealer::Sell(const char* i_strProductName, float f32Money)
{
using namespace adtf_util; //cStringUtil
//check whether the car is in stock
auto result = std::find_if(m_arrVehicles.begin(),
m_arrVehicles.end(),
[i_strProductName] (IVehicle* pVehicle)
{
return cStringUtil::IsEqual(pVehicle->Name(), i_strProductName);
});
if (m_arrVehicles.end() != result)
{
using namespace adtf::ucom; //latest ucom_cast<>
//get the price from the status symbol interface and sell the car if the price is right
if (ucom_cast<IStatusSymbol*>(*result)->Price() <= f32Money)
{
//the vehicle is sold by passing the ownership to the caller
IVehicle* pSoldVehicle = *result;
*result = nullptr;
--m_nVehicles;
return pSoldVehicle;
}
}
//either the car is not in stock or the customer doesn't have enough money
return nullptr;
}
//rented cars may not be modified in any way, so we return a pointer to a const vehicle
const IVehicle* cCarDealer::Rent(const char* i_strProductName) const
{
using namespace adtf_util; //cStringUtil
//check whether the car is in stock
auto result = std::find_if(m_arrVehicles.cbegin(),
m_arrVehicles.cend(),
[i_strProductName] (const IVehicle* pVehicle)
{
return cStringUtil::IsEqual(pVehicle->Name(), i_strProductName);
});
if ( m_arrVehicles.cend() != result )
{
return *result;
}
//car is not in stock
return nullptr;
}
void cCarDealer::Destroy()
{
//implemented empty as we just use operator delete in the context of this example
}
Namespace for the ADTF uCOM3 SDK.
ADTF adtf_util Namespace - Within adtf this is used as adtf::util or adtf_util and also defined as A_...

Exposing interfaces from inherited classes

The actual "casting" mechanism must be implemented by the user for which multiple ways exist. All of these ways have one thing in common: The actual casting process is performed within the scope of the IObject::GetInterface() methods and thus both methods must be implemented. To ensure, that a queried interface matches exactly one of the interfaces exposed by the implementation, the second parameter of ADTF_IID() is used. With the second parameter being a string, the entire matching process is broken down into string comparisons.

The following approaches exist:

The "traditional" way (aka "if-else-if-else")

The "traditional" way to implement the IObject::GetInterface() methods incorporates multiple if-else-if-else statements and the comparison of every single interface id with the requested interface id. If an interface id matches the requested one, a static_cast<> to the corresponding interface must be performed. To illustrate that, a traditional car dealer class is introduced. Take a look at the declaration of the IObject::GetInterface() methods of our cTraditionalCarCealer:

//file car_dealer.h
class cTraditionalCarDealer : public cCarDealer
{
public:
cTraditionalCarDealer(const char* i_strLocation);
virtual ~cTraditionalCarDealer() = default;
virtual const char* GetLocation() const;
private: //implementing IObject
virtual tResult GetInterface(const char* strIID, void*& o_pInterface);
virtual tResult GetInterface(const char* strIID, const void*& o_pInterface) const;
virtual void Destroy() const;
private:
const char* const m_strLocation; // Location of the car dealer
};

And the implementation:

//file car_dealer.cpp
cTraditionalCarDealer::cTraditionalCarDealer(const char* i_strLocation)
: cCarDealer(),
m_strLocation(i_strLocation)
{
}
const char* cTraditionalCarDealer::GetLocation() const
{
return m_strLocation;
}
tResult cTraditionalCarDealer::GetInterface(const char* i_strIID, const void*& o_pInterface) const
{
using namespace adtf_util; //cStringUtil
using namespace adtf::ucom; //get_iid<>()
//Compare the IIDs of all exposed interfaces with the requested one ("i_strIID").
//If the requested IID matches the IID of an exposed interface, static cast *this to exactly
//that interface.
if (cStringUtil::IsEqual(i_strIID, get_iid<IObject>())) //equal to IID provided by IObject?
{
o_pInterface = static_cast<const IObject*>(this);
}
//######################## uncommenting this leads to a compile error ########################
// else if(cStringUtil::IsEqual(i_strIID, get_iid<IStore>()))
// {
// o_pInterface = static_cast<const IStore*>(this);
// }
else if(cStringUtil::IsEqual(i_strIID, get_iid<IBuilding>())) //or maybe IBuilding?
{
o_pInterface = static_cast<const IBuilding*>(this);
}
else if(cStringUtil::IsEqual(i_strIID, get_iid<ICarDealer>())) //last ray of hope is ICarDealer
{
o_pInterface = static_cast<const ICarDealer*>(this);
}
else
{
o_pInterface = nullptr;
return ERR_NO_INTERFACE; //bummer...
}
return ERR_NOERROR;
}
tResult cTraditionalCarDealer::GetInterface(const char* i_strIID, void*& o_pInterface)
{
//avoid duplicate implementation: cast this to const to call the const version of GetInterface()
return static_cast<const cTraditionalCarDealer&>(*this).GetInterface(
i_strIID, const_cast<const void*&>(o_pInterface));
}
virtual tResult GetInterface(const char *i_strIID, void *&o_pInterface)=0
Query interfaces on an object.
ant::IObject IObject
Alias always bringing the latest version of ant::IObject into scope.
Definition: object_intf.h:102

Looking at the const version of IObject::GetInterface(), this approach becomes clear. If the requested string i_strIID is matched to an IID identifying an exposed interface, we static_cast<> our object to the output parameter o_pInterface. Note that the implementation only checks for the interfaces previously made accessible with ADTF_IID(). If this is not the case (e.g. IStore) an error during compile time will be the result.

Now let's take a look at the non-const version of IObject::GetInterface(). To reduce code duplication, the const version of IObject::GetInterface() to query the interface is used. It is vital to bear in mind, that this way, the casting mechanism must be implemented in the const version of GetInterface() while the non-const version - in turn - needs to call the const variant adding a const to the methods second parameter. Implementing it the other way around would cast away the constness, potentially leading to undefined behavior in some cases. Despite some rare cases in which the const and the non-const implementations of GetInterface() need to check for different interfaces, the described approach is the generally recommended one.

In summary, the "traditional" way gives the developer full control over his implementation and might be used where some extra functionality within the GetInterface() methods is required. However, with great power comes great responsibility and thus there are some serious drawbacks: One being a growing code base with every new interface that is to be exposed. Another one arises from the fact that the actual casting must be performed by the developer himself as the wrong interface might be casted (resulting from typical "copy-paste-replace" mistakes) - leading to hard-to-track errors and undefined behavior. To circumvent issues such as these, the following approach reduces the expense of manual comparisons.

The "advanced" way (aka "interface_expose<>")

To illustrate the "advanced" way to implement the IObject::GetInterface() methods we'll have a look at the cars the car dealers have to offer. As we all know, cars are at least two things: A vehicle and a status symbol. The cars our car dealers offer are no different from this. Before diving into the example code, take a look at the inheritance hierarchy of the class cCar which will serve as our example throughout this chapter:

The code for the basic vehicle interface looks like this:

//vehicles are objects and an environmental burden
class IVehicle : public adtf::ucom::IObject, public IEnvironmentalBurden
{
public:
ADTF_IID(IVehicle, "vehicle.adtf.example.iid");
virtual ~IVehicle() = default;
public: //pure virtual functions
virtual const char* Name() const = 0; //retrieves the name of the car
virtual bool UrbanApproved() const = 0; //indicates whether the car is urban approved
};

The IVehicle does not introduce anything new: It is derived from the IObject interface and is made accessible to the ucom_cast<> by implementing ADTF_IID().

In addition to being an object, vehicles also are an environmental burden. Thus our IVehicle inherits from interface IEnvironmentalBurden as well:

class IEnvironmentalBurden
{
public:
ADTF_IID(IEnvironmentalBurden, "environmental_burden.adtf.example.iid");
virtual ~IEnvironmentalBurden() = default;
public: //pure virtual functions
//returns the emission class of this object as character.
//possible characters are: 'A', 'B', 'C', 'D', 'E', 'F'
virtual char EmissionClass() const = 0;
};

Please consider two key aspects of the declaration of IEnvironmentalBurden interface. First: It is not derived from IObject. Second: Yet, it is accessible by ucom_cast<> by implementing ADTF_IID(). That's right, an interface does not need to be derived from IObject to be applicable for the ucom_cast<>. Please bear in mind, that the ucom_cast<> operator can only be used with object pointers of type IObject or descending types, respectively., However, concrete classes implementing IObject directly may as well expose additionally inherited interfaces even though these interfaces are not derived from IObject. Nonetheless, the respective interfaces still have to implement the ADTF_IID macro to be applicable for ucom_cast<> operators.

The overall principle has already been illustrated by the traditional way in the previous chapter. As shown in the source file of the car dealer the method cCarDealer::Sell() is using the ucom_cast<> to query an object of type IVehicle for its interface IStatusSymbol. The StatusSymbol interface is defined as follows:

class IStatusSymbol
{
public:
ADTF_IID(IStatusSymbol, "status_symbol.adtf.example.iid");
virtual ~IStatusSymbol() = default;
public: //pure virtual functions
//rating of this status symbol from 0 being the lowest rating to 10 being the highest rating
virtual uint32_t Rating() const = 0;
//every status symbol has a price
virtual float Price() const = 0;
};

IStatusSymbol is made accessible using ADTF_IID(), however, in this case, it is not derived from IObject. As our car implementation will later inherit from IVehicle and IStatusSymbol all together, calling the ucom_cast<> on an object of type IVehicle to query the interface of IStatusSymbol is perfectly correct:

IVehicle* cCarDealer::Sell(const char* i_strProductName, float f32Money)
{
using namespace adtf_util; //cStringUtil
//check whether the car is in stock
auto result = std::find_if(m_arrVehicles.begin(),
m_arrVehicles.end(),
[i_strProductName] (IVehicle* pVehicle)
{
return cStringUtil::IsEqual(pVehicle->Name(), i_strProductName);
});
if (m_arrVehicles.end() != result)
{
using namespace adtf::ucom; //latest ucom_cast<>
//get the price from the status symbol interface and sell the car if the price is right
if (ucom_cast<IStatusSymbol*>(*result)->Price() <= f32Money)
{
//the vehicle is sold by passing the ownership to the caller
IVehicle* pSoldVehicle = *result;
*result = nullptr;
--m_nVehicles;
return pSoldVehicle;
}
}
//either the car is not in stock or the customer doesn't have enough money
return nullptr;
}

Compared to the traditional way, the advanced way significantly reduces the effort of distinguishing all inherited interfaces individually: instead of querying for interfaces with an user given if-else-statement, this task is now done implicitly during compile time. This is achieved through the the meta struct template interface_expose.

//a car is a vehicle and a status symbol
class cCar : public IVehicle, public IStatusSymbol
{
private:
//we can alias the exposed interfaces anywhere in the code and use those in GetInterface()
IEnvironmentalBurden,
IStatusSymbol,
IVehicle> expose;
public:
//construct car with
cCar(char ui8EmissionClass,
uint32_t ui32Rating,
float f32Price,
const char* strColor,
bool bUrbanApproved);
//destructor
virtual ~cCar();
public: //implementing the interfaces
char EmissionClass() const;
uint32_t Rating() const;
float Price() const;
const char* Name() const;
bool UrbanApproved() const;
private: //IObject implementation
virtual void Destroy() const;
virtual tResult GetInterface(const char* strIID, void*& o_pInterface);
virtual tResult GetInterface(const char* strIID, const void*& o_pInterface) const;
private: //member section
const char m_ui8EmissionClass; //emission class
const uint32_t m_ui32Rating; //status symbol rating
const float m_f32Price; //price
const char* const m_strName; //name of the car
const bool m_bUrbanApproved; //whether it's urban approved
};
Meta template struct used to expose all interfaces.
Definition: adtf_iid.h:467

Using the interface_expose<> struct is straightforward. For convenience, we declare an alias of all the interfaces our implementation exposes - the actual interfaces we want to expose are given inside the angle brackets ("< >") as comma separated list. In this case the interfaces to expose are IObject, IEnvironmentalBurden, IStatusSytmbol and IVehicle. Furthermore the cCar class realizes all methods of its parent classes, including the GetInterface methods of IObject. Remember, the actual casting process must be performed as part of the GetInterface implementations! However, you might be surprised to see that we got rid of the entire boiler plate code initially introduced in the cTraditionalCarDealer just through the following amendments:

cCar::cCar(char ui8EmissionClass,
uint32_t ui32Rating,
float f32Price,
const char* strName,
bool bUrbanApproved) :
m_ui8EmissionClass(ui8EmissionClass),
m_ui32Rating(ui32Rating),
m_f32Price(f32Price),
m_strName(strName),
m_bUrbanApproved(bUrbanApproved)
{
std::cout << "Hello: " << strName << std::endl;
}
cCar::~cCar()
{
std::cout << "Bye: " << m_strName << std::endl;
}
char cCar::EmissionClass() const { return m_ui8EmissionClass; }
uint32_t cCar::Rating() const { return m_ui32Rating; }
float cCar::Price() const { return m_f32Price; }
const char* cCar::Name() const { return m_strName; }
bool cCar::UrbanApproved() const { return m_bUrbanApproved; }
void cCar::Destroy() const { delete this; }
tResult cCar::GetInterface(const char* strIID, void*& o_pInterface)
{ //use the interface_expose alias to check whether strIID identifies an exposed interface
return expose::Get(this, strIID, o_pInterface);
}
tResult cCar::GetInterface(const char* strIID, const void*& o_pInterface) const
{ //same here
return expose::Get(this, strIID, o_pInterface);
}

In the implemented GetInterface methods the static member function Get() of struct interface_expose<> (here called through the previously declared alias "expose") is called with three parameters:

  • A pointer to the object which exposes the interfaces (usually this)
  • the given IID of the interface the object is queried for as well as
  • the void pointer to which the queried interface is casted to.

That's it. No user-written if-else statement, no boiler plate code, no potential risks through unsafe casts. By calling Get on interface_expose<>, the compiler produces all the code required to query interfaces in a completely type-safe way. The code the compiler is generating is abstracted in pseudo-code below:

if (cStringUtil::IsEqual(Given_IID, get_iid<IObject>()))
{
InterfaceToReturn = static_cast<IObject*>(PointerToCompleteType);
}
else if (cStringUtil::IsEqual(Given_IID, get_iid<IEnvironmentalBurden>()))
{
InterfaceToReturn = static_cast<IEnvironmentalBurden*>(PointerToCompleteType);
}
else if (cStringUtil::IsEqual(Given_IID, get_iid<IStatusSymbol>()))
{
InterfaceToReturn = static_cast<IStatusSymbol*>(PointerToCompleteType);
}
else if (cStringUtil::IsEqual(Given_IID, get_iid<IVehicle>()))
{
InterfaceToReturn = static_cast<IVehicle*>(PointerToCompleteType);
}
else
{
InterfaceToReturn = nullptr;
return ERR_NO_INTERFACE;
}
return ERR_NOERROR;
Things to remember
  • Using interface_expose to expose the interfaces of an IObject implementation delegates the entire casting process to the compiler, thus providing a completely type safe way to perform the actual cast.

As convenient, safe and easy to use this approach might be, it is still not perfect. Wouldn't it be nice to also get rid of user implemented GetInterface methods entirely?

The "elaborate" way (aka "object<>")

Fortunately this is possible by deriving a concrete IObject implementation from the adtf::ucom::catwo::object class template. After taking a closer look at the next example code below, the approach becomes clear. It's time to introduce the cElaborateCar class:

//deriving from "object" with given parents and
//interfaces to expose compiles to the same code as "cCar"
class cElaborateCar : public adtf::ucom::object<IStatusSymbol, IEnvironmentalBurden, IVehicle>
{
public:
//construct car with
cElaborateCar(char ui8EmissionClass,
uint32_t ui32Rating,
float f32Price,
const char* strColor,
bool bUrbanApproved);
//destructor
virtual ~cElaborateCar();
public: //implementing the interfaces
char EmissionClass() const;
uint32_t Rating() const;
float Price() const;
const char* Name() const;
bool UrbanApproved() const;
private: //member section
const char m_ui8EmissionClass; //emission class
const uint32_t m_ui32Rating; //status symbol rating
const float m_f32Price; //price
const char* const m_strName; //name of the car
const bool m_bUrbanApproved; //whether it's urban approved
};
Use this template if you want to implement an ucom::ant::IObject based Interface and/or subclass an e...
Definition: object.h:359

Let's go through this code snippet step by step. We declare a class cElaborateCar which publicly inherits from the object<> template class. object<> takes an arbitrary amount of template parameters the the class will be derived from. In our example we tell the object<> to derive from IVehicle and IStatusSymbol and additionally expose IEnvironmentalBurden, leading to the class hierarchy as shown in diagram "The 'elaborate' way":

Both diagrams show the difference in the created class hierarchies when using the elaborate way in contrast to the advanced way as introduced in the previous chapter. The effective difference is, that object<> compiles to an intermediate class layer between the concrete implementation of the IObject (here cElaborateCar) and the parent classes to implement ( here IVehicle and IStatusSymbol). In short this means, that beside the intermediate layer of class object<>, the code created for cCar and cElaborateCar is exactly the same. The benefit in this scenario, is that the GetInterface methods are entirely compile- time generated and there is no need for manual implementations within cElaborateCar. The remaining user-defined implementation of cElaborateCar is reduced to what is shown below:

cElaborateCar::cElaborateCar(char ui8EmissionClass,
uint32_t ui32Rating,
float f32Price,
const char* strName,
bool bUrbanApproved) :
m_ui8EmissionClass(ui8EmissionClass),
m_ui32Rating(ui32Rating),
m_f32Price(f32Price),
m_strName(strName),
m_bUrbanApproved(bUrbanApproved)
{
}
cElaborateCar::~cElaborateCar()
{
}
char cElaborateCar::EmissionClass() const { return m_ui8EmissionClass; }
uint32_t cElaborateCar::Rating() const { return m_ui32Rating; }
float cElaborateCar::Price() const { return m_f32Price; }
const char* cElaborateCar::Name() const { return m_strName; }
bool cElaborateCar::UrbanApproved() const { return m_bUrbanApproved; }
Things to remember
  • Subclassing object creates an intermediate layer between the actual implementation of IObject and the interfaces inherited from.
  • The intermediate layer implements the GetInterface() methods automatically during compile time, using the interfaces given with as template parameters

Usage example

So, how can the ucom_cast<> be applied to the classes provided above? To show an example, the main application implements a class called cPerson which is interested in buying cars from our car dealers. This person looks like this:

//file application.cpp
class cPerson
{
ICarDealer* m_pCarDealer; //the current car dealer the person visits
std::unique_ptr<IVehicle> m_pOwnedCar; //the current vehicle this person owns
public:
//in the beginning the person neither visits any car dealer nor owns a car
cPerson()
: m_pCarDealer(nullptr),
m_pOwnedCar(nullptr)
{
}
//pass the store the person is supposed to be visiting
void VisitStore(ICarDealer& i_pStore)
{
m_pCarDealer = &i_pStore; //set the current store the person resides in
}
//get all provided information for the car with name 'i_strName' from current car dealer
bool GetCarInformation(const char* i_strName) const
{
//first we try to gather all information by trying to rent the wanted car
bool bInfoRetrieved = RentCar(i_strName);
//if renting fails, gather information by querying some interfaces on current car dealer
if (!bInfoRetrieved)
{
bInfoRetrieved = QueryStore(i_strName);
}
return bInfoRetrieved;
}
//buy the car with the name 'i_strName' and an amount of 'i_f32Money' from current car dealer
bool BuyCar(const char* i_strName, float i_f32Money)
{
//buy the car with the appropriate amount of money from the current car dealer
m_pOwnedCar.reset(m_pCarDealer->Sell(i_strName, i_f32Money));
if (nullptr != m_pOwnedCar)
{
std::cout << "Now owns car: " << i_strName << std::endl << std::endl;
}
return (nullptr != m_pOwnedCar.get());
}
//[...] //some more private code following later
};//class cPerson

So far the only unknown functionality cPerson introduces is the code inside method GetCarInformation(). For now we ignore the second part of this method and focus on the first part which retrieves all information from a specific car using private method RentCar():

private:
//rent car with name 'i_strName' and query for all information from this car's exposed interfaces
bool RentCar(const char* i_strName) const
{
//check whether the car dealer has the desired car in stock
using namespace adtf::ucom; //ucom_cast<>
//do we currently visit a car dealer that offers the desired car?
if ( nullptr != m_pCarDealer && m_pCarDealer->Offers(i_strName) )
{
//not all car dealers offer renting
const IVehicle* const pRentedCar = m_pCarDealer->Rent(i_strName);
if ( nullptr == pRentedCar )
{
std::cout << "-- Cannot rent vehicle: " << i_strName << std::endl;
return false;
}
//query all information provided by the vehicle interface
std::cout << "-- Rented vehicle: " << pRentedCar->Name() << std::endl;
std::cout << "------ Urban approved: " << std::boolalpha
<< pRentedCar->UrbanApproved() << std::endl;
std::cout << "------ Emission class: " << pRentedCar->EmissionClass() << std::endl;
//Get all remaining information from status symbol interface
//which must be queried from the vehicle using ucom_cast<>
const IStatusSymbol* const pStatusSymbol = ucom_cast<const IStatusSymbol*>(pRentedCar);
if (nullptr != pStatusSymbol)
{
std::cout << "------ Status symbol rating: " << pStatusSymbol->Rating()
<< "/10" << std::endl;
std::cout << "------ Status symbol price: " << pStatusSymbol->Price() << std::endl;
}
return true;
}
return false;
}

Within this method we rent the car from the currently visited car dealer. The complete code for the car dealer was introduced earlier on this page. Important to know is that ICarDealer::Rent() returns a pointer to an instance of type IVehicle from which all cars are derived from. This reference can be used to gather all information provided by the vehicle interface. The vehicle interface, however, does not provide other vital information such as the price of the car. To get these information we need to query the status symbol interface instead. As in this case the IVehicle is inherited by the cCar which also implements the IStatusSymbol, we can query the pointer to the vehicle interface for the IStatusSymbol interface using our ucom_cast<>. If the cast succeeded, the returned value points to the exposed interface IStatusSymbol of this particular rented car object.

Putting it all together, the main routine for a customer of type cPerson is provided below:

//file application.cpp
int main(int /* argc */, char* /* argv */[])
{
// //################################ Ordinary customer ##########################################
cPerson oOrdinaryCustomer; //this person visits the traditional car dealer and rents/buys cars
//the traditional car dealer is located some place nearby and offers four cars
std::unique_ptr<ICarDealer> pTradCarDealer(new cTraditionalCarDealer("Some place nearby"));
//parameters of the cars to add: 'EnvBurden', 'Rating', 'Price', 'Name', 'Urban'
pTradCarDealer->Add(new cCar ('A', 2, 12900.00, "Medium car", true ));
pTradCarDealer->Add(new cCar ('E', 7, 23900.00, "SUV", false));
pTradCarDealer->Add(new cElaborateCar('E', 9, 49900.00, "Sport coupe", false));
pTradCarDealer->Add(new cElaborateCar('D', 10, 79900.00, "Luxury car", false));
//visit the car dealer and try to rent some cars to retrieve the information
oOrdinaryCustomer.VisitStore(*pTradCarDealer);
std::cout << "Ordinary customer is visiting traditional car dealer: "
<< pTradCarDealer->GetLocation() << std::endl;
oOrdinaryCustomer.GetCarInformation("Medium car");
oOrdinaryCustomer.GetCarInformation("SUV");
oOrdinaryCustomer.GetCarInformation("Sport coupe");
oOrdinaryCustomer.GetCarInformation("Luxury car");
//after evaluating the alternatives, the responsible customer decides to buy the medium car
oOrdinaryCustomer.BuyCar("Medium car", 13000.00); //including tip
//[...] further code covering subsequent examples
return 0;
}

Which generates the following output:

Ordinary customer is visiting traditional car dealer: Some place nearby
-- Rented vehicle: Medium car
------ Urban approved: true
------ Emission class: A
------ Status symbol rating: 2/10
------ Status symbol price: 12900
-- Rented vehicle: SUV
------ Urban approved: false
------ Emission class: E
------ Status symbol rating: 7/10
------ Status symbol price: 23900
-- Rented vehicle: Sport coupe
------ Urban approved: false
------ Emission class: E
------ Status symbol rating: 9/10
------ Status symbol price: 49900
-- Rented vehicle: Luxury car
------ Urban approved: false
------ Emission class: D
------ Status symbol rating: 10/10
------ Status symbol price: 79900
Now owns car: Medium car

Exposing interfaces from member variables

Besides the obvious use case of exposing and querying interfaces in an inheritance hierarchy, it is also possible to expose and query for interfaces of member variables. This offers some pretty nice possibilities to divert the design of classes from specializations and generalizations to aggregations and compositions. In the example above, for instance, we queried all information from an object of type cCar by renting the car from a car dealer. But what if another car dealer does not want the customer to rent a car? In the lack of a corresponding object to type IVehicle to perform the query on, another way must exist to access all necessary information of a car. This can be realized by making the car dealer also exposing the corresponding interfaces of its member variables of type cCar. Consider the following code declaring another car dealer cCunningCarDealer:

//file car_dealer.h
class cCunningCarDealer: public adtf::ucom::object<IBuilding, IStore, ICarDealer>
{
private:
public:
//The store is created with one car in stock
cCunningCarDealer(const char* i_strLocation);
virtual ~cCunningCarDealer() = default;
public: //implement the interfaces
virtual const char* GetLocation() const;
virtual bool Offers(const char* i_strProductName) const;
virtual bool Add(IVehicle* i_pVehicle);
virtual IVehicle* Sell(const char* i_strProductName, float f32Money);
virtual const IVehicle* Rent(const char* i_strProductName) const;
private: //overwriting "cunning_default_object", using aliased "car_expose" from above
virtual tResult GetInterface(const char* strIID, void*& o_pInterface);
virtual tResult GetInterface(const char* strIID, const void*& o_pInterface) const;
private:
const char* const m_strLocation; // Location of the car dealer
std::unique_ptr<cElaborateCar> m_pCar; // The one car in stock
};

The cCunningCarDealer inherits from our object<>. The interfaces exposed by the cCunnningCarDealer are the same as the ones exposed by cTraditionalCarDealer, so no change there. But why does the cCunningCarDealer implement the GetInterface() methods, although this is already done by object<>?

This is to enable querying the member variable m_pCar for exposed interfaces. While object<> by itself can only expose the interfaces of heir class cCunningCarDealer. More precisely, first they delegate the call to the parent class object<>. If the call fails (meaning: no requested interface of cCunningCarDealer was not found) the GetInterface() method of cCar is called. interface_expose only offers to query for interface IStatusSymbol, so querying any other interface such as IVehicle or IEnvironmentalBurden will fail.

//file car_dealer.cpp
tResult cCunningCarDealer::GetInterface(const char* i_strIID, const void*& o_pInterface) const
{
//First call the GetInterface() method of the object implementation. If the interface
//is not exposed, check whether IStatusSymbol interface of the car member variable is queried
if (IS_OK(base_type::GetInterface(i_strIID, o_pInterface)))
{
return ERR_NOERROR;
}
else
{ //Query member variable m_pCar whether it exposes the interface identified with 'i_strIID'.
if (nullptr != m_pCar.get())
{ //The car is still in stock. Check whether IStatusSymbol was queried
//Instead of passing a this-pointer to interface_expose<>::Get(), we pass the pointer
//to the cCar object, so the actual cast is performed on 'm_pCar' if the IID matches
//Note: The only interface exposed for the car is the IStatusSymbol, so there is no way for any
//customer to inform itself about the 'IVehicle' information of a car
return adtf::ucom::interface_expose<IStatusSymbol>::Get(m_pCar.get(), i_strIID, o_pInterface);
}
}
return ERR_NO_INTERFACE;
}
tResult cCunningCarDealer::GetInterface(const char* i_strIID, void*& o_pInterface)
{
//avoid duplicate implementation: cast this to const to call the const version of GetInterface()
return static_cast<const cCunningCarDealer&>(*this).GetInterface(
i_strIID, const_cast<const void*&>(o_pInterface));
}
static tResult Get(Provider *i_pObj, const char *i_strIID, VoidType *&o_pInterface)
Get the interface with IID i_strIID exposed from i_pObj.
Definition: adtf_iid.h:589

In fact, the cCunningCarDealer uses exactly this mechanism to conceal any information about the environmental burden a car might impose. Because calling cCunningCarDealer::Rent() does not return any valid IVehicle information, the only possible way to retrieve this information would be to query the car dealer for the IVehicle interface of the car. However, this interface has not been exposed, by cCarDealer, resulting in failed attempts to retrieve any interfaces other than IStatusSymbol. Likewise, using the ucom_cast<> on the IStatusSymbol is impossible as it is not derived from IObject.

//file car_dealer.cpp
cCunningCarDealer::cCunningCarDealer(const char* i_strLocation)
: m_strLocation(i_strLocation),
m_pCar(new cElaborateCar('D', 10, 79900.00, "Luxury car", false)) //only one car in stock
{
}
const char* cCunningCarDealer::GetLocation() const
{
return m_strLocation;
}
bool cCunningCarDealer::Offers(const char* i_strProductName) const
{
return adtf_util::cStringUtil::IsEqual(i_strProductName, m_pCar->Name());
}
bool cCunningCarDealer::Add(IVehicle* /* i_pVehicle */)
{ //no new cars allowed to be added
return false;
}
IVehicle* cCunningCarDealer::Sell(const char* i_strProductName, float f32Money)
{
//sell the car if it is in stock and the price is right
if (Offers(i_strProductName) && (f32Money >= m_pCar->Price()))
{
return m_pCar.release(); //the new owner has to take care of cars destruction
}
return nullptr;
}
const IVehicle* cCunningCarDealer::Rent(const char* /* i_strProductName */) const
{
//this car dealer doesn't allow to rent cars
return nullptr;
}

To show an example of how this might be used, the following snippet shows the remaining part of the main applications cPerson:

//query the store for the car information
bool QueryStore(const char* i_strName) const
{
using namespace adtf::ucom; //ucom_cast<>
//do we currently visit a car dealer that offers the wanted car?
if ( nullptr != m_pCarDealer && m_pCarDealer->Offers(i_strName) )
{
//Query the vehicle information from the car dealer using ucom_cast<> on the car dealer
const IVehicle* const pVehicle = ucom_cast<const IVehicle*>(m_pCarDealer);
if (nullptr != pVehicle )
{
std::cout << "-- Queried store for vehicle: " << i_strName << std::endl;
std::cout << "------ Urban approved: " << std::boolalpha
<< pVehicle->UrbanApproved() << std::endl;
std::cout << "------ Emission class: " << pVehicle->EmissionClass() << std::endl;
}
else
{
std::cout << "---- Store does not provide any information for vehicle: "
<< i_strName << std::endl;
}
//Get all remaining information from status symbol interface
//which must be queried from the car dealer using ucom_cast<>
const IStatusSymbol* const pSymbol = ucom_cast<const IStatusSymbol*>(m_pCarDealer);
if (nullptr != pSymbol)
{
std::cout << "---- Queried store for status symbol information on vehicle: "
<< i_strName << std::endl;
std::cout << "------ Status symbol rating: " << pSymbol->Rating()
<< "/10" << std::endl;
std::cout << "------ Status symbol price: " << pSymbol->Price() << std::endl;
}
return true;
}
else
{
std::cout << "-- Store does not offer vehicle: " << i_strName << std::endl;
}
return false;
}

In addition to oOrdinaryCustomer, we create a rich customer that visits the cunning car dealer and buys a car despite lacking knowledge about the environmental burden:

//################################# Rich customer #############################################
cPerson oRichCustomer; //this customer visits the cunning car dealer
//the cunning car dealer is located some place far far away
std::unique_ptr<ICarDealer> pCunnCarDealer(new cCunningCarDealer("Some place far, far away"));
//visit the car dealer and try to get some information on the vehicle
oRichCustomer.VisitStore(*pCunnCarDealer);
std::cout << "Rich customer is visiting cunning car dealer: "
<< pCunnCarDealer->GetLocation() << std::endl;
oRichCustomer.GetCarInformation("Medium car"); //fails, car is not in stock
oRichCustomer.GetCarInformation("SUV"); //fails, car is not in stock
oRichCustomer.GetCarInformation("Sport coupe"); //fails, car is not in stock
oRichCustomer.GetCarInformation("Luxury car"); //succeeds, but no vehicle information are shown
//the rich customer buys the car regardless of the missing information about the environment
oRichCustomer.BuyCar("Luxury car", 80000.00); //again with tip included

Which generates the following output:

Rich customer is visiting cunning car dealer: Some place far, far away
-- Store does not offer vehicle: Medium car
-- Store does not offer vehicle: SUV
-- Store does not offer vehicle: Sport coupe
-- Cannot rent vehicle: Luxury car
---- Store does not provide any information for vehicle: Luxury car
---- Queried store for status symbol information on vehicle: Luxury car
------ Status symbol rating: 10/10
------ Status symbol price: 79900
Now owns car: Luxury car

Summary

Things to remember:

  • To use the ucom_cast<> functionality, include the following header.
    //for example in your "stdafx.h" file
    #include <adtf_ucom3.h>
  • The ucom_cast<> can only be applied to on objects descending from type IObject. Using it with other types leads to a compiler error during the build process. The following rules apply:
    • Inheriting from IObject enables the heir type to be usable with ucom_cast<>
    • Using ADTF_IID() in an interface makes the implementing interface accessible to the ucom_cast<>
    • The actual casting is performed as part of the user defined implementation, more specifically, as part of the IObject::GetInterface() methods
    • The ucom_cast<> itself wraps the casting process in a conveniently usable function template, which is used analogously to the standardized C++-style dynamic_cast<> operator
  • In an inheritance hierarchy, various interface types may or may not be made accessible to the ucom_cast<>
  • ucom_cast<> can be used to query objects of type IObject for interfaces which themselves are not necessarily derived from IObject.
  • Using interface_expose to expose the interfaces of an IObject implementation entirely delegates the actual casting process to the compiler, thus providing a completely type safe way to perform the actual cast.
  • Subclassing object creates an intermediate layer between the actual implementation and the interfaces given as parameters.
  • The intermediate layer transparently implements the IObject::GetInterface().

Copyright © Audi Electronics Venture GmbH. All rights reserved. (Generated on Thu Jun 9 2022 by doxygen 1.9.1)