The ucom_cast<>
provides a method to query an object of type
for interfaces it might expose. In short this means, the IObject
ucom_cast<>
dynamic_cast<>
, offering much more flexibility even without Runtime Type Information (RTTI)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:
IObject
enables the inherited type to be usable with ucom_cast<>
ADTF_IID()
in an interface makes the implementing interface accessible to the ucom_cast<>
IObject::GetInterface()
methodsucom_cast<>
itself wraps the casting process in a conveniently usable function template, which is used analogously to the standardized C++-style dynamic_cast<>
operatorThe C++ International Standard, section 5.2.7 [expr.dynamic.cast], defines the dynamic_cast<>
operator as follows:
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.
Let's start with a first glimpse at our basic example:
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.
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:
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:
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
.
The implementation of the cCarDealer
looks like this:
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 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:
And the implementation:
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.
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:
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
. The StatusSymbol
interface is defined as follows:
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
, 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:
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:
this
)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:
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?
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:
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:
object
creates an intermediate layer between the actual implementation of IObject
and the interfaces inherited from.GetInterface()
methods automatically during compile time, using the interfaces given with as template parametersSo, 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. 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:
Which generates the following output:
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
.
To show an example of how this might be used, the following snippet shows the remaining part of the main applications cPerson:
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:
Things to remember:
ucom_cast<>
functionality, include the following header. 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:IObject
enables the heir type to be usable with ucom_cast<>
ADTF_IID()
in an interface makes the implementing interface accessible to the ucom_cast<>
IObject::GetInterface()
methodsucom_cast<>
itself wraps the casting process in a conveniently usable function template, which is used analogously to the standardized C++-style dynamic_cast<>
operatorucom_cast<>
ucom_cast<>
can be used to query objects of type IObject
for interfaces which themselves are not necessarily derived from IObject
.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.object
creates an intermediate layer between the actual implementation and the interfaces given as parameters.IObject::GetInterface()
.