有没有办法设计两个私有操纵彼此数据的类?

时间:2011-10-04 22:37:59

标签: c++ class-design

背景:我正在为一个不是我编写的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)它没有很好的封装或直观。

因此,我再次问:有没有更好的方法来设计两个私有操纵彼此数据的类?

4 个答案:

答案 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.
{
}