- 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<>
- is an alternative for
dynamic_cast<>
, offering much more flexibility even without Runtime Type Information (RTTI)
- might be used to query objects for interfaces which are implemented using multiple inheritance
- 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.
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:
{
public:
ADTF_IID(IBuilding,
"building.adtf.example.iid");
virtual ~IBuilding() = default;
public:
virtual const char* GetLocation() const = 0;
};
#define ADTF_IID(_interface, _striid)
Common macro to enable correct treatment of interface classes by the adtf::ucom::ucom_cast<>
Base class for every interface type within the uCOM.
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:
virtual bool Offers(const char* i_strProductName) const = 0;
};
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:
virtual bool Add(IVehicle* i_pVehicle) = 0;
virtual IVehicle* Sell(const char* i_str, float f32Money) = 0;
virtual const IVehicle* Rent(const char* i_str) const = 0;
};
- 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:
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:
virtual void Destroy() const;
private:
std::array<IVehicle*, 4> m_arrVehicles;
size_t m_nVehicles;
};
The implementation of the cCarDealer
looks like this:
#include "stdafx.h"
#include "car.h"
#include "car_dealer.h"
cCarDealer::cCarDealer()
: m_arrVehicles(),
m_nVehicles(0)
{
m_arrVehicles = { nullptr, nullptr, nullptr, nullptr };
}
cCarDealer::~cCarDealer()
{
std::for_each(m_arrVehicles.begin(), m_arrVehicles.end(), [] (IVehicle* p) { delete p; });
}
bool cCarDealer::Offers(const char* i_strProductName) const
{
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)
{
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)
{
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)
{
if (ucom_cast<IStatusSymbol*>(*result)->Price() <= f32Money)
{
IVehicle* pSoldVehicle = *result;
*result = nullptr;
--m_nVehicles;
return pSoldVehicle;
}
}
return nullptr;
}
const IVehicle* cCarDealer::Rent(const char* i_strProductName) const
{
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;
}
return nullptr;
}
void cCarDealer::Destroy() const
{
}
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:
class cTraditionalCarDealer : public cCarDealer
{
public:
cTraditionalCarDealer(const char* i_strLocation);
virtual ~cTraditionalCarDealer() = default;
virtual const char* GetLocation() const;
private:
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;
};
A common result class usable as return value throughout.
And the implementation:
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
{
if (cStringUtil::IsEqual(i_strIID, get_iid<IObject>()))
{
o_pInterface =
static_cast<const IObject*
>(
this);
}
else if(cStringUtil::IsEqual(i_strIID, get_iid<IBuilding>()))
{
o_pInterface = static_cast<const IBuilding*>(this);
}
else if(cStringUtil::IsEqual(i_strIID, get_iid<ICarDealer>()))
{
o_pInterface = static_cast<const ICarDealer*>(this);
}
else
{
o_pInterface = nullptr;
return ERR_NO_INTERFACE;
}
return ERR_NOERROR;
}
tResult cTraditionalCarDealer::GetInterface(
const char* i_strIID,
void*& o_pInterface)
{
return static_cast<const cTraditionalCarDealer&>(*this).GetInterface(
i_strIID, const_cast<const void*&>(o_pInterface));
}
ant::IObject IObject
Alias always bringing the latest version of ant::IObject into scope.
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:
{
public:
ADTF_IID(IVehicle,
"vehicle.adtf.example.iid");
virtual ~IVehicle() = default;
public:
virtual const char* Name() const = 0;
virtual bool UrbanApproved() const = 0;
};
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:
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:
virtual uint32_t Rating() const = 0;
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)
{
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)
{
if (ucom_cast<IStatusSymbol*>(*result)->Price() <= f32Money)
{
IVehicle* pSoldVehicle = *result;
*result = nullptr;
--m_nVehicles;
return pSoldVehicle;
}
}
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
.
class cCar : public IVehicle, public IStatusSymbol
{
private:
IEnvironmentalBurden,
IStatusSymbol,
IVehicle> expose;
public:
cCar(char ui8EmissionClass,
uint32_t ui32Rating,
float f32Price,
const char* strColor,
bool bUrbanApproved);
virtual ~cCar();
public:
char EmissionClass() const;
uint32_t Rating() const;
float Price() const;
const char* Name() const;
bool UrbanApproved() const;
private:
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:
const char m_ui8EmissionClass;
const uint32_t m_ui32Rating;
const float m_f32Price;
const char* const m_strName;
const bool m_bUrbanApproved;
};
Meta template struct used to expose all interfaces.
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)
{
return expose::Get(this, strIID, o_pInterface);
}
tResult cCar::GetInterface(
const char* strIID,
const void*& o_pInterface)
const
{
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:
class cElaborateCar :
public adtf::ucom::object<IStatusSymbol, IEnvironmentalBurden, IVehicle>
{
public:
cElaborateCar(char ui8EmissionClass,
uint32_t ui32Rating,
float f32Price,
const char* strColor,
bool bUrbanApproved);
virtual ~cElaborateCar();
public:
char EmissionClass() const;
uint32_t Rating() const;
float Price() const;
const char* Name() const;
bool UrbanApproved() const;
private:
const char m_ui8EmissionClass;
const uint32_t m_ui32Rating;
const float m_f32Price;
const char* const m_strName;
const bool m_bUrbanApproved;
};
Use this template if you want to implement an ucom::ant::IObject based Interface and/or subclass an e...
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:
class cPerson
{
ICarDealer* m_pCarDealer;
std::unique_ptr<IVehicle> m_pOwnedCar;
public:
cPerson()
: m_pCarDealer(nullptr),
m_pOwnedCar(nullptr)
{
}
void VisitStore(ICarDealer& i_pStore)
{
m_pCarDealer = &i_pStore;
}
bool GetCarInformation(const char* i_strName) const
{
bool bInfoRetrieved = RentCar(i_strName);
if (!bInfoRetrieved)
{
bInfoRetrieved = QueryStore(i_strName);
}
return bInfoRetrieved;
}
bool BuyCar(const char* i_strName, float i_f32Money)
{
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());
}
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:
bool RentCar(const char* i_strName) const
{
if ( nullptr != m_pCarDealer && m_pCarDealer->Offers(i_strName) )
{
const IVehicle* const pRentedCar = m_pCarDealer->Rent(i_strName);
if ( nullptr == pRentedCar )
{
std::cout << "-- Cannot rent vehicle: " << i_strName << std::endl;
return false;
}
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;
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:
int main(int , char* [])
{
cPerson oOrdinaryCustomer;
std::unique_ptr<ICarDealer> pTradCarDealer(new cTraditionalCarDealer("Some place nearby"));
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));
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");
oOrdinaryCustomer.BuyCar("Medium car", 13000.00);
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:
{
private:
public:
cCunningCarDealer(const char* i_strLocation);
virtual ~cCunningCarDealer() = default;
public:
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:
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;
std::unique_ptr<cElaborateCar> m_pCar;
};
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.
tResult cCunningCarDealer::GetInterface(
const char* i_strIID,
const void*& o_pInterface)
const
{
if (
IS_OK(base_type::GetInterface(i_strIID, o_pInterface)))
{
return ERR_NOERROR;
}
else
{
if (nullptr != m_pCar.get())
{
}
}
return ERR_NO_INTERFACE;
}
tResult cCunningCarDealer::GetInterface(
const char* i_strIID,
void*& o_pInterface)
{
return static_cast<const cCunningCarDealer&>(*this).GetInterface(
i_strIID, const_cast<const void*&>(o_pInterface));
}
#define IS_OK(s)
Check if result is OK.
static tResult Get(Provider *i_pObj, const char *i_strIID, VoidType *&o_pInterface)
Get the interface with IID i_strIID exposed from i_pObj.
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
.
cCunningCarDealer::cCunningCarDealer(const char* i_strLocation)
: m_strLocation(i_strLocation),
m_pCar(new cElaborateCar('D', 10, 79900.00, "Luxury car", false))
{
}
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* )
{
return false;
}
IVehicle* cCunningCarDealer::Sell(const char* i_strProductName, float f32Money)
{
if (Offers(i_strProductName) && (f32Money >= m_pCar->Price()))
{
return m_pCar.release();
}
return nullptr;
}
const IVehicle* cCunningCarDealer::Rent(const char* ) const
{
return nullptr;
}
To show an example of how this might be used, the following snippet shows the remaining part of the main applications cPerson:
bool QueryStore(const char* i_strName) const
{
if ( nullptr != m_pCarDealer && m_pCarDealer->Offers(i_strName) )
{
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;
}
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:
cPerson oRichCustomer;
std::unique_ptr<ICarDealer> pCunnCarDealer(new cCunningCarDealer("Some place far, far away"));
oRichCustomer.VisitStore(*pCunnCarDealer);
std::cout << "Rich customer is visiting cunning car dealer: "
<< pCunnCarDealer->GetLocation() << std::endl;
oRichCustomer.GetCarInformation("Medium car");
oRichCustomer.GetCarInformation("SUV");
oRichCustomer.GetCarInformation("Sport coupe");
oRichCustomer.GetCarInformation("Luxury car");
oRichCustomer.BuyCar("Luxury car", 80000.00);
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.
- 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()
.