背景:我正在为一个不是我编写的C库编写C ++包装器接口。我的包装类模仿了库结构,在这个库中,struct b
的一些成员指向struct a
的成员。该库的文档说:“不要在struct a
之一之前销毁struct b
的变量。”实际上应该允许这样的情况,所以我想在代码中更好地处理这种情况。因此,在我的包装器中,如果class A
的一个实例class B
指向它的// some code shortened or not shown
struct a {
int d; // in reality, data is much more complicated
};
struct b {
int* d;
};
class B;
class A {
struct a a_;
vector<B*> registered_bs_; // should probably use unordered_set
public:
~A(void) { for (iterator it: registered_bs_) (*it)->copyA(); } // C++0x for
void registerB(B* b) { registered_bs_.push_back(b); }
void unregisterB(B* b) { registered_bs_.erase(b); } // find() code not shown
};
class B {
struct b b_;
A* pa_;
public:
B(A& a): b_(), pa_(0) { a.registerB(this); pa_ = &a; }
~B(void) { pa_->unregisterB(this); if (b_.d) delete b_.d; } // if B goes first
void copyA(void) { b_.d = new int(*b_.d); }
};
实例在B的所有实例之前被销毁,我想将数据从A复制到每个实例目前,我用公共成员函数来处理这个问题:
friend
从上面可以看出,寄存器和复制成员函数只是应只能从ctor / dtors调用。换句话说,我的类的用户永远不会调用这些函数。因此,根据封装原则和Scott Meyer的“使接口易于正确使用且难以正确使用”的理念,我想将这些功能放在A和B的私有部分中。但是,这显然意味着我再也无法从他们的同伴班级中调用他们了。我考虑过使用// this doesn't work
class B;
class A {
struct a a_;
vector<B*> registered_bs_;
void copyA(B& b) { b.b_.d = new int(*(b.b_.d)); } // circular
friend void B::registerB(A& a); // circular
friend void B::unregisterB(A& a); // circular
public:
~A(void) { for (iterator it: registered_bs_) copyA(*it); } // C++0x for
};
class B {
struct b b_;
A* pa_;
void registerB(A& a) { a.registered_bs_.push_back(this); }
void unregisterB(A& a) { a.registered_bs_.erase(this); } // find() not shown
friend void A::CopyA(B& b);
public:
B(A& a): b_(), pa_(0) { registerB(a); pa_ = &a; }
~B(void) { unregisterB(*pa_); if (b_.d) delete b_.d; }
};
函数,如下所示:
friend
但是,此代码至少有三个问题:1)存在无法解决的循环关系,2)每个类仍在尝试访问{{1}中其他类的私有成员声明,3)它没有很好的封装或直观。
因此,我再次问:有没有更好的方法来设计两个私有操纵彼此数据的类?
答案 0 :(得分:7)
是的,看看C ++朋友。
class B;
class A
{
friend class B;
// ...
};
class B
{
friend class A;
// ...
};
C ++常见问题解答有nice explanation of friendship。
答案 1 :(得分:2)
我真正想宣传的另一个不错的选择是passkey idiom。
class A;
class B;
class KeyForA{
KeyForA(){} // private ctor
friend class B;
};
class KeyForB{
KeyForB(){} // private ctor
friend class A;
};
class A{
// ...
public:
// ...
void registerB(B* b, KeyForA){ /*...*/ }
void unregisterB(B* b, KeyForA){ /*...*/ }
~A(){ for(auto it : registered_bs_) (*it)->copyA(KeyForB()); }
};
class B{
A* pa_
public:
B(A& a){ a.registerB(this, KeyForA()); pa_ = &a; }
~B(){ pa_->unregisterB(this, KeyForA()); /*...*/ }
};
重要的功能是公开的,但只有相应的类才能访问它们,因为没有其他人可以创建所需的“密钥”,这要归功于私人ctors和友谊。这也是非常精细的访问控制,因为它不仅像普通的友谊一样全面访问。
我希望这背后的概念是明确的,如果没有,请阅读链接。 This也可能是有意义的。
答案 2 :(得分:0)
您是否认为B
只有shared_ptr
对应A
?然后,当原始A
shared_ptr
消失时,引用它的任何B
都会将对象名义上保持在周围,直到所有引用B
实例也消失,此时它就会消失清理干净了。这将删除循环引用以及与私有数据交互的需要。
答案 3 :(得分:0)
要避免循环引用,请执行以下两项操作:
首先,声明彼此的类朋友,而不是尝试声明朋友方法。这意味着您必须更加小心,而不是将所有检查卸载到编译器。
其次,不要内联定义你的函数,而是像这样(大量缩短遗漏):
class A
{
void copyA(B& b);
};
class B
{
};
// B is now fully defined, can refer to members freely.
void A::copyA(B& b) // optional: Use the inline keyword before void.
{
}