Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I know the address of owner object in C++?

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.

like image 608
Xavier Nodet Avatar asked Apr 02 '09 13:04

Xavier Nodet


3 Answers

Take a look at the GoF Observer Design Patter.

like image 74
Mykola Golubyev Avatar answered Nov 19 '22 02:11

Mykola Golubyev


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.

like image 42
Evan Teran Avatar answered Nov 19 '22 01:11

Evan Teran


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.

like image 3
Luc Touraille Avatar answered Nov 19 '22 00:11

Luc Touraille