Получение класса 'parent' или 'host' элемента данных без потери памяти - PullRequest
2 голосов
/ 21 сентября 2010

Предположим, у меня есть класс MyClass, к которому я хочу добавить определенное поведение "наблюдателя". Тогда я мог бы определить класс следующим образом:

class MyClass : public IObserver
{
...
};

Теперь предположим, что эта функция «наблюдателя» не имеет прямого отношения к классу, но относится к членам данных, хранящимся в классе. Например. элемент данных указывает на другой класс OtherClass, и ему необходимо установить значение NULL, если экземпляр, на который он ссылается, удален:

class PointerToOtherClass : public IObserver
{
...
};

class MyClass
{
private:
   PointerToOtherClass m_ptr;
};

В этом случае мы могли бы написать это намного проще, используя умный указатель.

Теперь предположим, что вместо того, чтобы просто указывать значение NULL, если экземпляр OtherClass удален, мы хотим также удалить MyClass. Поэтому уже недостаточно иметь PointerToOtherClass в качестве наблюдателя, MyClass также должен быть наблюдателем. Но это означает, что член данных m_ptr не может самостоятельно реализовать полную функциональность (изменения его значения), но также должен поместить некоторую функциональность в родительский класс.

Решением может быть передача указателя на MyClass члену PointerToOtherClass. Если MyClass затем реализует наблюдателя, то процесс «регистрации» и «отмены регистрации» наблюдателя MyClass может быть легко выполнен экземпляром PointerToOtherClass.

template <typename ParentType>
class PointerToOtherClass
{
public:
   PointerToOtherClass(ParentType *parent) : m_parent(parent) {}
   void setValue (OtherClass *c) { /* unregister/register m_parent */ }
private:
   ParentType *m_parent;
};

class MyClass : public IObserver
{
public:
   MyClass() : m_ptr(this) {}
private:
   PointerToOtherClass m_ptr;
};

Хотя это работает правильно и может быть обобщено как своего рода умный указатель, мы жертвуем 4 байта (32-битная среда), потому что элемент данных должен указывать на своего родителя. Это не кажется много, но может быть существенным, если в приложении есть миллионы экземпляров MyClass, а MyClass имеет десять или более таких элементов данных.

Поскольку m_ptr является членом MyClass, похоже, что можно получить указатель на MyClass, начиная с указателя на m_ptr. Или, другими словами: методы в PointerToOtherClass должны иметь возможность преобразовывать свой указатель this в указатель на MyClass, вычитая смещение m_ptr в MyClass.

Это дало мне идею написать это шаблонным способом. В следующем коде шаблонный класс шаблона HostAwareField обращается к своему родителю, вычитая смещение, которое передается в качестве аргумента шаблона:

#include <iostream>

typedef unsigned char Byte;

template <typename ParentType,size_t offset>
class HostAwareField
   {
   public:
      ParentType *getParent() const {return (ParentType *)(((Byte *)this)-offset);}
      void printParent() {std::cout << "Parent=" << getParent()->m_name << std::endl;}
   };

class X
   {
   public:
      X (char *name) : m_name(name) {}
      char *m_name;
      HostAwareField<X,offsetof(X,m_one)> m_one;
   };

void main()
{
std::cout << "X::m_one: offset=" << offsetof(X,m_one) << std::endl;

X x1("Ross");
X x2("Chandler");
X x3("Joey");

x1.m_one.printParent();
x2.m_one.printParent();
x3.m_one.printParent();
}

Однако это не компилируется. Сообщает о следующих ошибках:

test.cpp(18) : error C2027: use of undefined type 'X'
        test.cpp(14) : see declaration of 'X'
test.cpp(18) : error C2227: left of '->m_one' must point to class/struct/union/generic type
test.cpp(16) : error C2512: 'HostAwareField' : no appropriate default constructor available
test.cpp(26) : error C2039: 'm_two' : is not a member of 'X'
        test.cpp(14) : see declaration of 'X'
test.cpp(32) : error C2662: 'HostAwareField<ParentType,offset>::printParent' : cannot convert 'this' pointer from 'HostAwareField' to 'HostAwareField<ParentType,offset> &'
        Reason: cannot convert from 'HostAwareField' to 'HostAwareField<ParentType,offset>'
        Conversion requires a second user-defined-conversion operator or constructor
test.cpp(33) : error C2662: 'HostAwareField<ParentType,offset>::printParent' : cannot convert 'this' pointer from 'HostAwareField' to 'HostAwareField<ParentType,offset> &'
        Reason: cannot convert from 'HostAwareField' to 'HostAwareField<ParentType,offset>'
        Conversion requires a second user-defined-conversion operator or constructor
test.cpp(34) : error C2662: 'HostAwareField<ParentType,offset>::printParent' : cannot convert 'this' pointer from 'HostAwareField' to 'HostAwareField<ParentType,offset> &'
        Reason: cannot convert from 'HostAwareField' to 'HostAwareField<ParentType,offset>'
        Conversion requires a second user-defined-conversion operator or constructor
test.cpp(36) : error C2039: 'm_two' : is not a member of 'X'
        test.cpp(14) : see declaration of 'X'
test.cpp(36) : error C2228: left of '.printParent' must have class/struct/union
test.cpp(37) : error C2039: 'm_two' : is not a member of 'X'
        test.cpp(14) : see declaration of 'X'
test.cpp(37) : error C2228: left of '.printParent' must have class/struct/union
test.cpp(38) : error C2039: 'm_two' : is not a member of 'X'
        test.cpp(14) : see declaration of 'X'
test.cpp(38) : error C2228: left of '.printParent' must have class/struct/union

Если я изменю следующую строку:

HostAwareField<X,offsetof(X,m_one)> m_one;

к этой строке:

HostAwareField<X,4> m_one;

Тогда этот код работает правильно, но требует, чтобы я «вычислял» смещение вручную, что может привести к ошибкам при добавлении, удалении или реорганизации элементов данных.

Это означает, что, хотя я не могу автоматизировать это, я мог бы жестко закодировать смещения (как значение 4 выше) и выполнить проверку (чтобы увидеть, действительно ли 4 является смещением m_one в классе) впоследствии, но это требует дополнительные ручные проверки, делающие всю систему не водонепроницаемой.

Есть ли способ получить правильно скомпилированный исходный код выше? Или есть еще один способ добиться того, что я хочу сделать?

Ответы [ 2 ]

1 голос
/ 21 сентября 2010

Я считаю, что член должен быть полностью объявлен до того, как offsetof может быть использован для определения его смещения. Это имеет смысл, потому что тип элемента может влиять на его смещение из-за правил выравнивания байтов. (На самом деле может быть так, что весь класс должен быть объявлен первым тоже.)

В HostAwareField<X,offsetof(X,m_one)> m_one; тип требует offsetof для работы, прежде чем он может быть полностью объявлен. Но offsetof требует, чтобы тип был объявлен прежде, чем он сможет работать. Я не думаю, что есть какой-то способ сделать эту компиляцию.

Я не могу придумать ни одной простой модификации этого проекта, которая заставила бы его работать, не требуя дополнительных байтов на элемент, что, конечно, противоречит заявленной цели.

Возможно, вы могли бы пересмотреть общий дизайн, чтобы сделать включающий класс наблюдателем. Затем отправьте его соответствующему члену и изучите какое-либо возвращаемое значение, чтобы определить, нужно ли отменить регистрацию или выполнить какие-либо действия, необходимые для включающего класса.

0 голосов
/ 21 сентября 2010

Я буду честен, я не читал все это.Тем не менее, моя внутренняя реакция на первую часть заключается в том, что у вас может быть запах кода, даже если вы хотите знать, к какому классу принадлежит данная переменная.Рассматривали ли вы Boost.Signals как альтернативное решение?Это реализация паттерна Observer таким образом, чтобы отделить наблюдателя от наблюдателя.Это может решить вашу проблему необходимости знать слишком много о внутренностях участвующих классов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...