Introduction
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.
Basics
Implementing interfaces applicable for the ucom_cast<>.
Example
car.h
class IEnvironmentalBurden
{
public:
ADTF_IID(IEnvironmentalBurden,
"environmental_burden.adtf.example.iid");
virtual ~IEnvironmentalBurden() = default;
public:
virtual char EmissionClass() const = 0;
};
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;
};
{
public:
ADTF_IID(IVehicle,
"vehicle.adtf.example.iid");
virtual ~IVehicle() = default;
public:
virtual const char* Name() const = 0;
virtual bool UrbanApproved() const = 0;
};
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;
};
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;
};
#define ADTF_IID(_interface, _striid)
Definition adtf_iid.h:19
A_UTILS_NS::cResult tResult
For backwards compatibility and to bring latest version into scope.
Definition result.h:736
ant::IObject IObject
Alias always bringing the latest version of ant::IObject into scope.
Definition object_intf.h:109
ant::interface_expose< Interfaces... > interface_expose
Alias bringing the latest version of meta struct template ant::interface_expose into scope.
Definition adtf_iid.h:817
car.cpp
#include "stdafx.h"
#include "car.h"
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);
}
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; }
car_dealer.h
class IVehicle;
{
public:
ADTF_IID(IBuilding,
"building.adtf.example.iid");
virtual ~IBuilding() = default;
public:
virtual const char* GetLocation() const = 0;
};
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;
};
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;
};
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;
};
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;
};
{
private:
typedef adtf::ucom::object<IBuilding, IStore, ICarDealer> base_type;
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:
private:
const char* const m_strLocation;
std::unique_ptr<cElaborateCar> m_pCar;
};
tResult GetInterface(const char *i_strIID, void *&o_pInterface) override
Query interfaces on an object.
Definition object.h:401
car_dealer.cpp
#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
{
using namespace adtf_util;
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)
{
using namespace adtf_util;
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)
{
{
IVehicle* pSoldVehicle = *result;
*result = nullptr;
--m_nVehicles;
return pSoldVehicle;
}
}
return nullptr;
}
const IVehicle* cCarDealer::Rent(const char* i_strProductName) const
{
using namespace adtf_util;
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
{
}
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;
{
o_pInterface =
static_cast<const IObject*
>(
this);
}
{
o_pInterface = static_cast<const IBuilding*>(this);
}
{
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));
}
void cTraditionalCarDealer::Destroy() const
{
delete this;
}
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;
}
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 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)
{
return static_cast<const cCunningCarDealer&>(*this).GetInterface(
i_strIID, const_cast<const void*&>(o_pInterface));
}
#define IS_OK(s)
Check if result is OK.
Definition result.h:17
Namespace for the ADTF uCOM SDK.
Definition adtf_system.h:324
InterfacePointerType ucom_cast(ObjectPointerType i_pObject) noexcept
Alias always bringing the latest version of ant::ucom_cast() into scope.
Definition ucom_cast.h:157
constexpr const char * get_iid() noexcept
Alias bringing the latest version of ant::get_iid() into scope.
Definition adtf_iid.h:429
Implementing interfaces
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.
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.
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.
Exposing interfaces
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 "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.
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 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.
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.
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.
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.
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, IStatusSymbol 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.
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:
{
InterfaceToReturn =
static_cast<IObject*
>(PointerToCompleteType);
}
{
InterfaceToReturn = static_cast<IEnvironmentalBurden*>(PointerToCompleteType);
}
{
InterfaceToReturn = static_cast<IStatusSymbol*>(PointerToCompleteType);
}
{
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::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.
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 ault_objectNote[style="dotted", dir=none];
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:
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
application.cpp
#include "stdafx.h"
#include "car.h"
#include "car_dealer.h"
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());
}
private:
bool RentCar(const char* i_strName) const
{
using namespace adtf::ucom;
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;
}
bool QueryStore(const char* i_strName) const
{
using namespace adtf::ucom;
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;
}
};
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);
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);
return 0;
}
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:
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().
Within this method we rent the car from the currently visited car dealer.
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.
The main routine 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:
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.
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.
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.
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:
- 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().