Inverting One Relation
The following code sample declares a relation data member car in which a Person owns a Car.
class Car:
public IlsObject
{
};
class Person:
public IlsObject
{
public:
IlsOwns<Person,Car> car;
Person();
};
Such a relation has a single origin: a car is owned by one and only one person. To invert the car relation, you just have to maintain one back pointer to its origin. To do so, use the class template IlsInvertedRelation, as follows:
class Car:
public IlsObject
{
public:
Car(): person(*this){}
IlsInvertedRelation<Car,Person> person;
};
The inverted relation
person must be a data member of the target class
Car. The class
Car must directly or transitively derive from
IlsObject. Derivation must be public. The class
Person can derive from both
IlsObject or
IlsEntity. For details, see
Server Objects.
Once this back pointer has been defined, you must make sure that it points to the right object. In other words, you want to avoid having, on the one hand, a person pointing to a given car and, on the other hand, having that car pointing to another person.
Back Pointers
Rogue Wave® Server supplies two callbacks, setContext and unsetContext, that help you keep an inverse relation consistent in a transparent way. Both these callbacks are user-defined functions which must be declared in the public part of the target class—Car in our example—like this:
class Car:
public IlsObject
{
public:
void setContext(Person& p, void*, IlsRelationId){person=&p;}
void unsetContext(Person&, void*, IlsRelationId){person=0;}
Car(): person(*this){}
private:
IlsInvertedRelation<Car,Person> person;
};
These functions take the following arguments:
A reference to the origin of the relation or to an instance of a class from which the origin is derived.
A pointer to the relation itself or to
void*. In our example, it could be
IlsOwns<Person,Car>*, but since we do not make use of this information,
we have just used
void* instead for convenience.
When you build a relation without specifying its identifier, Rogue Wave Server supplies a default value. This value is an instance of IlsDefaultRelationId.
The function setContext is automatically called when a relation is established: it sets the back pointer to Person. The function unsetContext is automatically called when a relation is broken off: its sets the back pointer to 0.
Inverting Relations with Different Origins
You can invert several relations with the same target but different origin-types. To do so, you have to declare as many pairs of overloaded setContext and unsetContext functions as there are origins.
In addition to the relation binding Person to Car, we can have the following relation:
class Garage:
public IlsObject
{
public:
IlsOwns<Garage,Car> car;
Garage();
};
To get the various origins of a car object, you have to declare two pairs of setContext and unsetContext callbacks as follows.
class Car:
public IlsObject
{
public:
Car(): person(*this), garage(*this){}
// relation inversion: Person
void setContext(Person& p, void*, IlsRelationId) {person=&p;}
void unsetContext(Person&, void*, IlsRelationId) {person=0;}
// relation inversion: Garage
void setContext(Garage& p, void*, IlsRelationId) {garage=&p;}
void unsetContext(Garage&, void*, IlsRelationId) {garage=0;}
private:
IlsInvertedRelation<Car,Person> person;
IlsInvertedRelation<Car,Garage> garage;
};
We could also declare a single pair of functions taking as their first argument a reference to a base class common to both Person and Garage. Let us call this base class CarHolder:
class CarHolder:
public IlsObject
{
public:
//...
};
class Person:
public CarHolder
{
public:
IlsOwns<Person,Car> car;
Person();
};
class Garage:
public CarHolder
{
public:
IlsOwns<Garage,Car> car;
Garage();
};
The callbacks setContext and unsetContext now have the following aspect:
class Car:
public IlsObject
{
public:
void setContext(CarHolder& h, void*, IlsRelationId){_holder=&h;}
void unsetContext(CarHolder&, void*, IlsRelationId){_holder=0;}
Car();
private:
IlsInvertedRelation<Car,CarHolder> _holder;
};
This solution has several advantages:
Objects of type
Car are smaller;
Functions applying to objects of type
Car that need to “know” the car owner do not have to perform unnecessary “if then” tests to check whether the owner is of type
Person or of type
Garage.
The classes
Car and
CarHolder remain completely independent of the various potential owners of a car, and thus offer greater reusability. They can be put in a separate library. It will not be necessary to modify the class
Car in case it becomes the target of a relation with a new origin-class.
Inverting one Relation Among Several
You can also invert a particular relation among a set of relations that have the same target.
To do so, you have to declare a pair of public setContext and unsetContext functions which can be called in any case.
Lets us assume that you want to invert the relation Person/Car but not the relation Garage/Car. If you define only the following pair of functions:
void setContext(Person& p, void*, IlsRelationId) {person=&p;}
void unsetContext(Person&, void*, IlsRelationId) {person=0;}
when the Garage/Car relation is instantiated, C++ attempts to call the setContext and unsetContext functions providing Garage as first argument, and a type problem occurs. In order for the calls to setContext and unsetContext with Garage as first argument to be accepted, you need to define two “dummy” functions:
void setContext(IlsRefCounted&, void*, IlsRelationId){}
void unsetContext(IlsRefCounted&, void*, IlsRelationId){}
Since IlsRefCounted is the base class of IlsObject and IlsEntity, you will be able to provide any type deriving from these classes as the first argument.
Actually, when you define a relation, Rogue Wave Server calls no-op predefined setContext and unsetContext callbacks that are included in a base class of the target class.
However, when you define new pairs of callbacks in a derived class, these callbacks overload the predefined inherited ones. In addition, the C++ algorithm used to solve overloading operations does not utilize inheritance. As a consequence, the predefined callbacks are hidden. If you forget to specify them, the compiler will generate an error message. The rule of thumb consists in systematically adding these callbacks to each class which is the target of at least one relation.
Using Relation Identifiers with an Inverted Relation
The functions setContext and unsetContext take the type IlsRelationId as their third argument. This argument is used to identify various relations of the same type with the same origin.
Consider a Car that owns objects of type Wheel:
class Wheel:
public IlsObject
{
};
typedef IlsSmartPointer<Wheel> WheelP;
class Car:
public IlsObject
{
public:
IlsOwns<Car,Wheel> fl,fr,rl,rr;
Car(WheelP frontLeft, WheelP frontRight, WheelP rearLeft,
WheelP rearRight):
fl(*this,frontLeft), fr(*this,frontRight),
rl(*this,rearLeft), rr(*this,rearRight)
{}
};
Let us suppose that we want to maintain a back pointer from Wheel to Car for the front right wheel only. To do so, we must use the last argument of a relation declaration, that is, IlsRelationId. We declare an enumerated type in the class Car that stores the type of the wheel, and we associate each relation with an identifier.
The code for the class Car would be the following:
class Car:
public IlsObject
{
public:
enum {FRONT_LEFT,FRONT_RIGHT,REAR_LEFT,REAR_RIGHT};
IlsOwns<Car,Wheel> fl,fr,rl,rr;
Car(WheelP frontLeft,
WheelP frontRight,
WheelP rearLeft,
WheelP rearRight):
fl(*this,frontLeft,FRONT_LEFT), fr(*this,frontRight,FRONT_RIGHT),
rl(*this,rearLeft,REAR_LEFT), rr(*this,rearRight,REAR_RIGHT)
{}
};
The code for the class Wheel would be the following:
class Wheel:
public IlsObject
{
public:
wheel(): car(*this){}
void setContext(Car& car, void*, IlsRelationId id);
void unsetContext(Car&, void*, IlsRelationId id);
private:
IlsInvertedRelation<Wheel,Car> car;
};
The code for the functions setContext and unsetContext would be the following:
void Wheel::setContext(Car& c, void*, IlsRelationId id) {
if (id==Car::FRONT_RIGHT) car=&c;
}
void Wheel::unsetContext(Car&, void*, IlsRelationId){
if (id==Car::FRONT_RIGHT) car=0;
}
Version 6.3
Copyright © 2018, Rogue Wave Software, Inc. All Rights Reserved.