假设我有一个MyClass类,我想添加某些'observer'行为。然后我可以像这样定义类:
class MyClass : public IObserver
{
...
};
现在假设这个'观察者'功能与类没有直接关系,而是与存储在类中的数据成员直接相关。例如。数据成员指向另一个类OtherClass,如果它引用的实例被删除,则需要将其设置为NULL:
class PointerToOtherClass : public IObserver
{
...
};
class MyClass
{
private:
PointerToOtherClass m_ptr;
};
在这种情况下,我们甚至可以使用智能指针更简单地写这个。
现在假设如果删除了OtherClass实例,而不是仅仅将指针置于NULL,我们也想删除MyClass。因此,让PointerToOtherClass成为观察者是不够的,MyClass也应该是观察者。 但是,这意味着数据成员m_ptr本身不能实现(改变其值)的全部功能,但也需要在父类中添加一些功能。
解决方案可能是将指向MyClass的指针传递给PointerToOtherClass成员。如果MyClass然后实现了观察者,则可以通过PointerToOtherClass实例轻松完成“注册”和“取消注册”MyClass观察者的行为。
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的偏移量),但这需要额外的人工检查,使整个系统不防水。
有没有办法让上面的源代码正确编译?或者还有另一种技巧可以实现我想做的事情吗?
答案 0 :(得分:1)
我认为必须在offsetof
之前完全声明成员才能确定其偏移量。这是有道理的,因为成员的类型可能会因字节对齐规则而影响其偏移量。 (实际上可能也必须首先宣布整个班级。)
在HostAwareField<X,offsetof(X,m_one)> m_one;
中,类型需要offsetof
才能完全声明。但是offsetof
要求在它可以工作之前声明类型。我认为没有办法进行编译。
另一方面,我想不出对这种设计的任何简单修改都会使其工作而不需要每个成员额外的字节数,这当然会违背规定的目的。
也许您可以修改整体设计,使封闭类成为观察者。然后让它调度到适当的成员并检查某种返回值,以确定是否需要取消注册或执行封闭类所需的任何操作。
答案 1 :(得分:0)
我会说实话,我没有读完这一切。但是,我对第一部分的直觉反应是,即使想知道给定变量属于哪个类,您也可能会遇到Code Smell。您是否将Boost.Signals视为替代解决方案?它是Observer模式的一种实现,它以一种分离观察者和观察者的方式。它可以解决你需要对所涉及的类的内部过多了解的问题。