Справочная информация: я пишу интерфейс оболочки C ++ для библиотеки C, написанной не мной. Мои классы-оболочки имитируют структуры библиотеки, и в этой библиотеке некоторые члены struct b
указывают на члены struct a
. Документация к этой библиотеке гласит: «Не уничтожайте переменную struct a
перед одной из struct b
». На самом деле есть ситуации, когда это должно быть разрешено, поэтому я хочу лучше справиться с ситуацией в коде. Поэтому в моей оболочке, если экземпляр class A
с одним или несколькими экземплярами class B
, указывающими на него, будет уничтожен до всех этих экземпляров B, я хочу скопировать данные из A в каждый экземпляр 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); }
};
Как видно из вышесказанного, функции-члены register и copy являются только и должны вызываться только из ctor / dtors. Другими словами, пользователи моих классов не должны никогда вызывать эти функции. Следовательно, в соответствии с принципами инкапсуляции и философией Скотта Майера «сделать интерфейсы простыми в использовании, правильными и трудными в неправильном использовании», я хочу поместить эти функции в частные разделы A и B. Однако это, очевидно, означает, что я больше не мог вызывать их из класса сверстников. Я рассмотрел использование friend
функций следующим образом:
// 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; }
};
Однако в этом коде есть как минимум три неправильные вещи: 1) существуют циклические отношения, которые не могут быть разрешены, 2) каждый класс все еще пытается получить доступ к закрытым членам другого класса в объявлениях friend
и 3) он не очень хорошо инкапсулирован или интуитивно понятен.
Поэтому я снова спрашиваю: есть ли лучший способ спроектировать два класса, которые конфиденциально манипулируют данными друг друга?