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 member 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?