2009-04-02 17 views
7

Ich möchte in C++ eine Notifier-Klasse erstellen, die ich in anderen Objekten verwenden werde, um verschiedene Halter zu benachrichtigen, wenn das Objekt zerstört wird.Wie kann ich die Adresse des Besitzerobjekts in C++ wissen?

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; 
}; 

Mein Punkt ist, dass ich ein dichtes und komplizierte Objektgraphen habe, würde ich in dem Anmelder die Adresse des im Besitz Objekts vermeiden möge zu speichern. Gibt es eine Möglichkeit, meine Benachrichtigungsklasse so zu ändern, dass sie die Adresse des eigenen Objekts von seiner eigenen Adresse und einem Offset ableiten kann, der zur Kompilierzeit berechnet wird?

Beachten Sie auch, dass jedes Objekt möglicherweise mehrere "Eigentümer" melden muss, möglicherweise aus der gleichen Klasse.

Danke.

+0

Benötigen Sie statische Polymorphie oder können Sie eine abstrakte Basisklasse IOwner mit z. eine rein virtuelle Methode 'notify'? –

+0

Nein, ich kann keine reine virtuelle "Benachrichtigung" haben. –

Antwort

2

Oder so etwas wie:

Vererben von Ihrem Anmelder und fügen Sie als Template-Parameter gehört.Dann können Sie eine prozentige Methode innerhalb der Anmelder zur Verfügung haben:

template < class Owner , class Owned > 
class Notifier 
{ 
public: 
    Notifier(Owner* owner) 
    {} 

    Owned * owned() 
    { return static_cast< Owned * >(this); } 

    ~Notifier() 
    { 
     // notify owner with owned() 
    } 
}; 

class Owner 
{}; 

class Owned : public Notifier< Owner , Owned > 
{ 
public: 
    Owned(Owner * owner) : Notifier< Owner , Owned >(owner) 
    {} 
}; 
+0

+1. Das ist meiner Meinung nach die beste Lösung. Es erlaubt jedoch nicht mehrere Besitzer desselben Typs. –

+0

@Luc: Ja, es erlaubt mehrere Besitzer des gleichen Typs mit nur ein bisschen mehr Arbeit: z. ein int Template-Parameter ... Siehe meine Antwort. –

+0

Diese Lösung ist nur anwendbar, wenn der Objektgraph fast statisch ist (nicht mehr als ein Objekt desselben Typs). Wenn Sie Instanzen dynamisch in einer Baumstruktur erstellen, müssen die Instanzen des Eigentümers gespeichert werden. – mmmmmmmm

1

Ein Teil der Lösung wäre, dass der Besitz von Notifier erben würde. Auf diese Weise wird die Adresse des Objekts zerstört ist einfach ‚this‘ ...

class Owned : public Notifier<Owner> { 
public: 
    Owned(Owner* owner) 
    : Notifier<Owner>(owner) 
    {} 
}; 

Aber wie aus der gleichen Klasse mehr ‚Besitzer‘ zu behandeln? Wie kann man mehrfach von der 'selben Klasse' erben?

Dank fa's answer, hier ist die Lösung, die ich suchte:

#include <iostream> 

template <class Owner, class Owned, int = 0> 
class Notifier { 
public: 
    Notifier(Owner* owner) 
    : _owner(owner) 
    {} 
    ~Notifier() { 
    _owner->remove(owned()); 
    } 
    Owned * owned(){ 
    return static_cast< Owned * >(this); 
    } 

private: 
    Owner* _owner; 
}; 

class Owner { 
public: 
    void remove(void* any) { 
    std::cout << any << std::endl; 
    } 
}; 

class Owned : public Notifier<Owner,Owned,1>, Notifier<Owner,Owned,2> { 
public: 
    Owned(Owner* owner1, Owner* owner2) 
    : Notifier<Owner,Owned,1>(owner1) 
    , Notifier<Owner,Owned,2>(owner2) 
    {} 
}; 

int main() { 
    std::cout << sizeof(Owned) << std::endl; 
    Owner owner1; 
    Owner owner2; 
    Owned owned(&owner1, &owner2); 
    std::cout << "Owned:" << (void*)&owned << std::endl << std::endl; 
} 

Dank!

+0

Können Sie im Notifier keine Eigentümerliste anstatt einer einzigen speichern? –

+0

Ihr Use Case ermöglicht es Ihnen wirklich, dies alles zur Kompilierzeit zu erfahren? Und Sie sind in Ordnung mit dem Code Bloat von Instanziieren so viele Vorlagen? – rmeador

+0

Der Anwendungsfall besteht darin, die Beziehungen zwischen Klassen in einem Business-Objektmodell zu implementieren. Z.B. eine Nachfrage ist mit einem Kunden verknüpft: Ich weiß alles über diese Klassen, und ich weiß, dass jede Nachfrage genau 1 Kunde hat, und dass jeder Kunde 0 zu n Forderungen hat ... –

6

Werfen Sie einen Blick auf die GoF Observer Design Patter.

+0

Ich bin in der Tat auf der Suche nach einer Möglichkeit, ein zu implementieren Eins-zu-viele-Beziehung. Es kann sehr gut für ein Beobachtermuster verwendet werden, aber nicht unbedingt. –

+0

@ Xavier: Ich bin mir nicht sicher, ob ich dich verstehe. "Sehr gut für einen Beobachter verwendet werden". Mit Observer können Sie Ihre Benachrichtigung implementieren. –

+0

"Der Observer definiert eine Eins-zu-viele-Beziehung, so dass die anderen automatisch benachrichtigt und aktualisiert werden, wenn ein Objekt seinen Status ändert." Aber ich möchte, dass die Vielen den einen benachrichtigen (auf eine speichereffiziente Weise). –

0

Ich bezweifle es sehr. Der Notifier kann nicht wissen, dass er in der Zusammensetzung verwendet wurde. Was passiert, wenn ich

class Foo 
{ 
private: 
    Notifier _a, _b, _c; 
} 

tun würde ich gerne obwohl falsch bewiesen werden, aber ich bezweifle wirklich, ohne explizit zu geben weitere Informationen zu den Notifier machbar ist.

+0

Irgendein Vorlagen- (oder anderer) Trick, um diese Informationen zur Kompilierzeit zu geben? –

+0

Es gibt keine Möglichkeit, wie Vorlagen Ihnen hier helfen können. –

3

Es wäre ein bösen Hack und wahrscheinlich nicht garantiert werden, aber hier ist ein Gedanke ich diese nicht empfehlen.

Angenommen, Sie haben Ihr Layout wie Sie wie folgt beschrieben:

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; 
}; 

Wenn _notifier seinen Namen kennt, könnte es Owned ‚s-Adresse wie folgt aus (die in der Notifier ausgeführt wird‘ berechnen Konstruktor):

Owned *p = reinterpret_cast<Owned *>(reinterpret_cast<char *>(this) - offsetof(Owned, _notifier)); 

Grundsätzlich ist die Annahme, dass _notifier bei einigen festen Offset innerhalb der Owned-Klasse ist. Daher ist die Adresse von Owned gleich _notifiers Adresse abzüglich des gleichen Offsets.

Noch einmal, dies ist undefiniertes Verhalten, das ich nicht empfehlen würde, aber möglicherweise funktionieren könnte.

3

fa.'s answer ist ein guter Anfang. Es löst jedoch nicht das Problem, mehrere Besitzer desselben Typs zu haben. Eine Lösung besteht darin, dass der Anmelder eine Liste von Besitzern anstatt einer einzigen speichert. Hier ist eine schnelle Implementierung ist, die Idee zu zeigen:

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; 
}; 

Sie können es auf diese Weise verwenden:

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 dem Fall, dass Sie viele verschiedene Arten von Eigentümer haben, kann diese Lösung wird ziemlich schwierig benutzen. In diesem Fall sollten Sie bei den Boost-metaprogramming Bibliotheken suchen (MPL, Fusion), mit dem Sie mit einem Code könnte am Ende, dass Sie so tun stopft lassen:

class Owned : public Notifier<Owned, OwnerType1, OwnerType1, OwnerType2> 
{ 
    Owned(OwnerType1 & o1, OwnerType1 & o2, OwnerType2 & o3) 
     : base(o1,o2,o3) 
}; 

jedoch die Implementierung dieser Lösung würde etwas länger sein als die vorherige.

+0

Dies ist sehr interessant, wenn man eine dynamische Liste von Besitzern benötigt. In meinem Fall weiß ich im Voraus, wie viele Besitzer ich von jedem Typ habe (normalerweise eins, manchmal zwei, nie mehr). –