开发者

Getting the 'parent' or 'host' class of a data member without sacrificing memory

开发者 https://www.devze.com 2023-01-16 20:14 出处:网络
Suppose I have a class MyClass to which I want to add certain \'observer\' behavior.Then I could define the class like this:

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
{
...
}开发者_如何学Python;

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?


I believe a member has to be fully declared before offsetof can be used to determine its offset. This makes sense because the type of a member can affect its offset due to byte-alignment rules. (It may actually be that the entire class has to be declared first too.)

In HostAwareField<X,offsetof(X,m_one)> m_one; the type requires offsetof to work before it can be fully declared. But offsetof requires the type to be declared before it can work. I don't think there's any way to make that compile.

Offhand, I cannot think of any simple modification to this design that will make it work without needing extra bytes per member, which of course defeats the stated purpose.

Perhaps you could revise the overall design to make the enclosing class the observer. Then have it dispatch to the appropriate member and examine some kind of return value to determine if it needs to unregister or perform whatever action is necessary on the enclosing class.


I'll be honest, I didn't read all that. However, my gut reaction to the first part is you might have a Code Smell in even wanting to know what class a given variable belongs to. Have you considered Boost.Signals as an alternate solution? It's an implementation of the Observer pattern in a way that decouples the observer and observee. It could solve your problem of needing to know too much about the internals of the classes involved.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号