开发者

Is there a way to design two classes that privately manipulate each other's data?

Background: I'm writing a C++ wrapper interface for a C library not written by me. My wrapper classes mimic the library structs, and in this library, some members of struct b point to members of struct a. The documentation for this library says, "Don't destroy a variable of struct a before one of struct b." There are actually situations where this should be allowed, so I want to handle the situation better in code. Therefore, in my wrapper, should an instance of class A with one or more instances of class B pointing to it be destroyed before all of those instances of B, I want to copy the data from A to every instance of B. Currently, I handle this with public membe开发者_运维问答r functions, as such:

// 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); }
};

As can be seen from the above, the register and copy member functions are only and should only be called from ctor/dtors. In other words, users of my classes should never call these functions. Therefore, in keeping with the principles of encapsulation and Scott Meyer's philosophy of "make interfaces easy to use correctly and difficult to use incorrectly," I want to put those functions in the private sections of A and B. However, this obviously means that I could no longer invoke them from their peer class. I've considered using friend functions, as follows:

// 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; }
};

However, there are at least three things wrong with this code: 1) there's a circular relationship that can't be resolved, 2) each class is still trying to access private members of the other class in the friend declarations, and 3) it isn't well encapsulated or intuitive.

Therefore, I ask again: is there a better way to design two classes that privately manipulate each other's data?


Yes, look into C++ friends.

class B;

class A
{
    friend class B;
    // ...
};

class B
{
    friend class A;
    // ...
};

The C++ FAQ has a nice explanation of friendship.


Another nice option that I really like to advertise is the 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()); /*...*/ }
};

The important functions are public, but only the respective classes will ever have access to them, because no-one else can create the needed "key", thanks to the private ctors and friendship. This also very fine grained access control, as it not just grants access across the board like normal friendship.

I hope the concept behind this is clear, if not, read the link. This might also be of interest.


Did you consider just having B have a shared_ptr to its corresponding A? Then when the original A shared_ptr goes away, any B's that reference it keep the object nominally around until all the referencing B instances also go away at which point it's cleaned up. This removes the circular reference and the need to interact with private data.


To avoid the circular references, do two things:

First, declare the classes friends of each other, rather than trying to declare friend methods. This means you will have to be more careful rather than offloading all the checking to your compiler.

Second, don't define your functions inline, instead like this (a great deal of shortening omissions):

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.
{
}
0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜