代码之家  ›  专栏  ›  技术社区  ›  Patrick

在不牺牲内存的情况下获取数据成员的“parent”或“host”类

  •  2
  • Patrick  · 技术社区  · 16 年前

    假设我有一个类MyClass,我想向它添加一些“观察者”行为。然后我可以这样定义类:

    class MyClass : public IObserver
    {
    ...
    };
    

    现在假设这个“观察者”功能不是直接与类相关的,而是与存储在类中的数据成员相关的。例如,一个数据成员指向另一个类OtherClass,如果它引用的实例被删除,则需要将其设置为空:

    class PointerToOtherClass : public IObserver
    {
    ...
    };
    
    class MyClass
    {
    private:
       PointerToOtherClass m_ptr;
    };
    

    在这种情况下,我们甚至可以使用智能指针编写更简单的代码。

    现在假设,如果删除了OtherClass实例,我们也希望删除MyClass,而不只是将指针放为空。所以,让PointerToOtherClass作为观察者已经不够了,MyClass也应该是一个观察者。 但是,这意味着数据成员m_ptr不能单独实现完整的功能(更改其值),但还需要在父类中放置一些功能。

    解决方案可以是将指向MyClass的指针传递给PointerToOtherClass成员。如果MyClass随后实现了Observer,那么PointerToOtherClass实例可以轻松地执行“注册”和“注销”MyClass Observer的操作。

    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的成员,所以看起来应该可以从m浐ptr的指针开始获取指向myClass的指针。或者,换句话说:PointerToOtherClass中的方法应该能够通过减去MyClass中m_ptr的偏移量,将其“this”指针转换为指向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 回复  |  直到 15 年前
        1
  •  1
  •   TheUndeadFish    15 年前

    我认为会员必须在 offsetof 可用于确定其偏移量。这是有意义的,因为成员的类型会由于字节对齐规则而影响其偏移量。(实际上,可能必须首先声明整个类。)

    HostAwareField<X,offsetof(X,m_one)> m_one; 类型需要 离岸价 在它被完全声明之前工作。但是 离岸价 要求先声明该类型,然后它才能工作。我认为没有任何方法可以编译它。

    顺便说一下,我想不出任何简单的设计修改,如果不需要每个成员额外的字节就可以使它工作,这当然会破坏所述的目的。

    也许您可以修改总体设计,使封闭类成为观察者。然后让它分派到适当的成员,并检查某种返回值,以确定它是否需要注销或执行封闭类上所需的任何操作。

        2
  •  0
  •   Michael Kristofik    15 年前

    老实说,我没读过这些。不过,我对第一部分的直觉反应是,您可能会有代码味道,甚至想知道给定变量属于哪个类。你考虑过吗 Boost.Signals 作为替代方案?它是观察者模式的一种实现,以一种将观察者和观察者分离的方式。它可以解决您需要对所涉及的类的内部了解太多的问题。