I would like to create in C++ a Notifier class that I will use in other objects to notify various holders when the object gets destroyed.
template <class Owner>
class Notifier<Owner> {
public:
Notifier(Owner* owner);
~Notifier(); // Notifies the owner that an object is destroyed
};
class Owner;
class Owned {
public:
Owned(Owner* owner);
private:
Notifier<Owner> _notifier;
};
My point is that as I have a dense and complicated object graph, I'd like to avoid storing the address of the owned object in the notifier. Is there a way to change my notifier class so that it can deduce the owned object's address from its own address and an offset that would be computed at compile time?
Note also that any object may have to notify several 'owners', possibly from the same class.
Thanks.
Take a look at the GoF Observer Design Patter.
It would be a nasty hack and probably not guaranteed to work, but here's a thought I don't recommend this.
Suppose you have your layout like you described like this:
template <class Owner>
class Notifier<Owner> {
public:
Notifier(Owner* owner);
~Notifier(); // Notifies the owner that an object is destroyed
};
class Owner;
class Owned {
public:
Owned(Owner* owner);
private:
Notifier<Owner> _notifier;
};
If _notifier
knows its name, it could calculate Owned
's address like this (which is executed in the Notifier
's constructor):
Owned *p = reinterpret_cast<Owned *>(reinterpret_cast<char *>(this) - offsetof(Owned, _notifier));
basically, the assumption is that _notifier is at some fixed offset within the Owned class. Therefore the address of Owned is equal to _notifier
's address minus that same offset.
Once again, this is undefined behavior which I wouldn't recommend, but could possibly work.
fa.'s answer is a good start. However, it does not resolve the problem of having multiple owners of the same type. One solution is to have the notifier store a list of owners instead of a single one. Here is a quick implementation, to show the idea:
template <typename Owner, typename Owned>
class Notifier
{
protected:
Notifier()
{}
// Constructor taking a single owner
Notifier(Owner & o)
{
owners.push_back(&o);
}
// Constructor taking a range of owners
template <typename InputIterator>
Notifier(InputIterator firstOwner, InputIterator lastOwner)
: owners(firstOwner, lastOwner) {}
~Notifier()
{
OwnerList::const_iterator it = owners.begin();
OwnerList::const_iterator end = owners.end();
for ( ; it != end ; ++it)
{
(*it)->notify(static_cast<Owned*>(this));
}
}
// Method for adding a new owner
void addOwner(Owner & o)
{
owners.push_back(&o);
}
private:
typedef std::vector<Owner *> OwnerList;
OwnerList owners;
};
You can use it this way:
class Owner;
class Owned : public Notifier<Owner, Owned>
{
typedef Notifier<Owner, Owned> base;
//Some possible constructors:
Owned(Owner & o) : base(o) { }
Owned(Owner & o1, Owner & o2)
{
base::addOwner(o1); //qualified call of base::addOwner
base::addOwner(o2); //in case there are other bases
}
Owned(std::list<Owner*> lo) : base(lo.begin(), lo.end()) { }
};
In the case where you have many different types of Owners, this solution can become rather difficult to use. In this case, you might want to look at the boost metaprogramming libraries (MPL, Fusion), with which you could end up with a code that let you do stuffs like that:
class Owned : public Notifier<Owned, OwnerType1, OwnerType1, OwnerType2>
{
Owned(OwnerType1 & o1, OwnerType1 & o2, OwnerType2 & o3)
: base(o1,o2,o3)
};
However, implementing this solution would be a little longer than the previous one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With