Предположим, у меня есть класс 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 в классе) впоследствии, но это требует дополнительные ручные проверки, делающие всю систему не водонепроницаемой.
Есть ли способ получить правильно скомпилированный исходный код выше? Или есть еще один способ добиться того, что я хочу сделать?