Как я могу узнать адрес объекта-владельца в C++?
-
22-08-2019 - |
Вопрос
Я хотел бы создать на C++ класс Notifier, который я буду использовать в других объектах для уведомления различных владельцев об уничтожении объекта.
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;
};
Я хочу сказать, что, поскольку у меня плотный и сложный граф объектов, я бы не хотел хранить адрес принадлежащего объекта в уведомителе.Есть ли способ изменить мой класс уведомителя, чтобы он мог выводить адрес принадлежащего объекта из своего собственного адреса и смещения, которое будет вычисляться во время компиляции?
Также обратите внимание, что любому объекту может потребоваться уведомить нескольких «владельцев», возможно, из одного и того же класса.
Спасибо.
Решение
Или что-то вроде этого:
Наследуйте от вашего уведомителя и добавьте Owned в качестве параметра шаблона.Затем вы можете иметь собственный метод, доступный внутри уведомителя:
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 )
{}
};
Другие советы
Взгляните на Шаблон дизайна GoF Observer.
Это было бы противный взломать и, вероятно, не гарантированно сработает, но вот мысль Я не рекомендую это.
Предположим, у вас есть макет, который вы описали следующим образом:
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;
};
Если _notifier
знает свое имя, он может вычислить Owned
такой адрес (который выполняется в Notifier
конструктор):
Owned *p = reinterpret_cast<Owned *>(reinterpret_cast<char *>(this) - offsetof(Owned, _notifier));
по сути, предполагается, что _notifier находится на некотором фиксированном смещении внутри класса Owned.Поэтому адрес Owned равен _notifier
адрес минус то же самое смещение.
Еще раз: это неопределенное поведение, которое я бы не рекомендовал, но оно, возможно, сработает.
ответ фа. это хорошее начало.Однако это не решает проблему наличия нескольких владельцев одного типа.Одним из решений является сохранение в уведомителе списка владельцев вместо одного.Вот быстрая реализация, чтобы продемонстрировать идею:
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;
};
Вы можете использовать его следующим образом:
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()) { }
};
Если у вас много разных типов владельцев, это решение может оказаться довольно сложным в использовании.В этом случае вы можете взглянуть на библиотеки метапрограммирования Boost (МПЛ, Слияние), с помощью которого вы можете получить код, позволяющий делать подобные вещи:
class Owned : public Notifier<Owned, OwnerType1, OwnerType1, OwnerType2>
{
Owned(OwnerType1 & o1, OwnerType1 & o2, OwnerType2 & o3)
: base(o1,o2,o3)
};
Однако реализация этого решения займет немного больше времени, чем предыдущее.
Частью решения было бы наследование Owned от Notifier.Таким образом, адрес уничтоженного объекта будет просто «это»…
class Owned : public Notifier<Owner> {
public:
Owned(Owner* owner)
: Notifier<Owner>(owner)
{}
};
Но как справиться с несколькими «владельцами» одного и того же класса?Как можно несколько раз наследовать от «одного и того же класса»?
Благодаря ответ Фа, вот решение, которое я искал:
#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;
}
Спасибо!
Я в этом очень сомневаюсь.Уведомитель не может узнать, что он использовался в композиции.Что, если я сделаю
class Foo
{
private:
Notifier _a, _b, _c;
}
Хотя мне бы очень хотелось, чтобы меня доказали, что я ошибаюсь, но я действительно сомневаюсь, что это выполнимо без явного предоставления дополнительной информации уведомителю.