2009-04-02 22 views
7

Me gustaría crear en C++ una clase Notifier que usaré en otros objetos para notificar a varios titulares cuando el objeto se destruya.¿Cómo puedo saber la dirección del objeto propietario en C++?

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

Mi punto es que como tengo un gráfico objeto denso y complicado, me gustaría evitar el almacenamiento de la dirección del objeto de propiedad en el notificador. ¿Hay alguna manera de cambiar mi clase notificador para que pueda deducir la dirección del objeto propiedad desde su propia dirección y un desplazamiento que se computaría en tiempo de compilación?

Tenga en cuenta también que cualquier objeto puede tener que notificar a varios "propietarios", posiblemente de la misma clase.

Gracias.

+0

¿Necesita polimorfismo estático o puede crear una clase base abstracta IOwner con p. Ej. un método virtual puro 'notificar'? –

+0

No, no puedo tener una 'notificación' virtual pura. –

Respuesta

2

O algo así:

hereda de sus notificador y añadir controladas en parámetro de plantilla.A continuación, puede tener un método de propiedad disponibles dentro del notificador:

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. Esa es, en mi opinión, la mejor solución. Sin embargo, no permite múltiples propietarios del mismo tipo. –

+0

@Luc: Sí, permite varios propietarios del mismo tipo con solo un poco más de trabajo: agregar, p. un parámetro de plantilla int ... Ver mi respuesta. –

+0

Esta solución solo es aplicable si el gráfico de objetos es casi estático (no más de un objeto del mismo tipo). Si crea instancias dinámicamente de forma similar a un árbol, necesita que se almacenen las instancias del propietario. – mmmmmmmm

1

Parte de la solución sería heredar de Notifier. De esta manera, la dirección del objeto destruido es simplemente 'esto' ...

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

Pero cómo manejar múltiples 'dueños' de la misma clase? ¿Cómo se puede heredar varias veces de la "misma clase"?

Gracias a fa's answer, aquí está la solución que estaba buscando:

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

Gracias!

+0

En el notificador, ¿no puede almacenar una lista de propietarios en lugar de una sola? –

+0

¿Su caso de uso realmente le permite saber todo esto en tiempo de compilación? ¿Estás de acuerdo con que el código inflado genere instancias de tantas plantillas? – rmeador

+0

El caso de uso es implementar las relaciones entre clases en un Modelo de objeto de negocio. P.ej. una Demanda está vinculada a un Cliente: sé todo sobre esas clases, y sé que cada demanda tiene exactamente 1 cliente, y que cada cliente tiene 0 a n demandas ... –

6

Eche un vistazo a GoF Observer Design Patter.

+0

Estoy buscando una forma de implementar una relación de uno a muchos. Se puede usar para un patrón Observer, pero no necesariamente. –

+0

@Xavier: No estoy seguro de que te entendamos. "muy bien ser usado para un observador". Observer es la forma en que puede implementar su notificación. –

+0

"The Observer define una relación de uno a muchos para que cuando un objeto cambie de estado, los otros se notifiquen y actualicen automáticamente". Pero quiero que muchos notifiquen el uno (de una manera eficiente con la memoria). –

0

Lo dudo mucho. No hay forma de que el notificador sepa que se ha usado en composición. ¿Qué pasa si

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

Me encantaría estar equivocado, sin embargo, pero realmente dudo que sea factible, sin dar más información de forma explícita al notificador.

+0

¿Algún truco de plantilla (u otro) para proporcionar esta información en tiempo de compilación? –

+0

No hay forma de que las plantillas lo ayuden aquí. –

3

Sería un truco desagradable y probablemente no se garantiza que funcione, pero aquí hay un pensamiento No recomiendo este.

Suponga que tiene su diseño como usted describió así:

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

Si _notifier sabe su nombre, se podría calcular 'dirección de s como esto (que se ejecuta en el Notifier' Owned constructor s):

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

básicamente, la suposición es que _notifier está en algún desplazamiento fijo dentro de la clase de propiedad. Por lo tanto, la dirección del propietario es igual a la dirección _notifier menos la misma compensación.

Una vez más, este es un comportamiento indefinido que no recomendaría, pero podría funcionar.

3

fa.'s answer es un buen comienzo. Sin embargo, no resuelve el problema de tener múltiples propietarios del mismo tipo. Una solución es hacer que el notificador almacene una lista de propietarios en lugar de una sola. He aquí una rápida implementación, para mostrar la 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; 
}; 

Usted puede usarlo de esta manera:

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()) { } 
}; 

En el caso de que usted tiene muchos tipos diferentes de Propietarios, esta solución puede llegar a ser bastante difícil usar. En este caso, es posible que desee ver en las bibliotecas impulso metaprogramación (MPL, Fusion), con el que podría terminar con un código que le permiten hacer telas como esa:

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

Sin embargo, la implementación de esta solución ser un poco más largo que el anterior.

+0

Esto es muy interesante si se necesita una lista dinámica de propietarios. En mi caso, sé de antemano cuántos propietarios tengo de cada tipo (generalmente uno, a veces dos, nunca más). –

Cuestiones relacionadas