Suppose I have a class MyClass to which I want to add certain 'observer' behavior. Then I could define the class like this:
class MyClass : public IObserver
{
...
};
Now suppose this this 'observer' functionality is not directly related to the class, but to data members stored in the class. E.g. a data member points to another class OtherClass and it needs to be set to NULL if the instance it refers to is deleted:
class PointerToOtherClass : public IObserver
{
...
};
class MyClass
{
private:
PointerToOtherClass m_ptr;
};
In this case, we could even write this much simpler using a smart pointer.
Now suppose that instead of just putting the pointer to NULL if the OtherClass instance is deleted, we want to delete MyClass as well. So it's not sufficient anymore to have PointerToOtherClass being an observer, MyClass should be an observer as well. But, this means that the data member m_ptr cannot implement the full functionality (of changing its value) on its own, but also needs to put some functionality in the parent class.
A solution could be to pass a pointer to MyClass to the PointerToOtherClass member. If MyClass then implements the observer, the act of 'registering' and 'unregistering' the MyClass observer can be easily done by the PointerToOtherClass instance.
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;
};
Althoug this works correctly and can be generalized as a kind of smart pointer, we sacrifice 4 bytes (32-bit environment) because the data member needs to point to its parent. This doesn't seem much but can be substantial if there are millions of instances of MyClass in the application, and MyClass has ten or more of these data members.
Since m_ptr is a member of MyClass, it looks like it should be possible to get the pointer to MyClass starting from a pointer to m_ptr. Or, in other words: the methods in PointerToOtherClass should be able to convert its 'this' pointer to a pointer to MyClass by subtracting the offset of m_ptr in MyClass.
This gave me the idea of writing this in a templated way. In the following code, the templated HostAwareField template class accesses its parent by subtracting the offset which is passed as template argument:
#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();
}
However, this does not compile. It reports the following errors:
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
If I change the following line:
HostAwareField<X,offsetof(X,m_one)> m_one;
to this line:
HostAwareField<X,4> m_one;
Then this code works correctly, but requires me to 'calculate' the offset manually, possibly leading to errors if data members are added, removed or reorganized.
This means that although I cannot automate this, I could hard-code the offsets (like the value 4 above) and perform a check (to see if 4 is really the offset of m_one in the class) afterwards, but this requires additional manual checks, making the whole system not waterproof.
Is there a way to get the source code above correctly compiled? Or is there another trick to achieve what I want to do?