Я столкнулся с проблемой в нескольких контекстах, когда класс содержит объект, который, хотя и не связан каким-либо ужасно близким образом, разделяет значительную часть своего состояния с другим объектом. Это вызывает у меня некоторые проблемы, поскольку в результате я получаю объекты, которые в несколько раз больше, чем они должны быть из-за перекрывающегося состояния, и пару раз я сталкивался с ужасными ошибками из-за того, что не мог должным образом сохранять состояния, заблокированные вместе. Я не смог найти удовлетворительного ответа на эти вопросы, поэтому мне интересно, есть ли какие-либо парадигмы C ++, которые решают эту проблему.
В качестве минимального примера, предположим, у меня есть следующеекод, который в основном моделирует псевдо-поток на графе, отслеживая избыток в каждом узле графа - и делает это таким образом, что агрессивно поддерживает инвариант, что избыток узла является суммой потоков в него минус суммаиз потоков из него:
struct Node{
int excess = 0;
};
struct Flow{
Node* const a;
Node* const b;
int flow = 0;
public:
Flow(Node* a, Node* b):a(a),b(b){}
void setFlow(int newFlow){
int dif = newFlow - flow;
a->excess -= dif;
b->excess += dif;
flow = newFlow;
};
~Flow(){
setFlow(0);
}
};
В некотором более позднем пункте в коде есть другой объект, который, среди прочего, взаимодействует с сетью потоков некоторым сложным способом, где некоторая пара узлов важнадля самого класса, но интерфейс для безопасного создания потока между ними также желателен. Итак, в коде у нас есть некоторый больший класс, который где-то в своем определении имеет следующие фрагменты:
class UnrelatedObject{
//...
Node* const a;
Node* const b;
//...
Flow f = {a,b};
//...
};
Проблема в том, что этот объект хранит данные избыточно - он имеет (исключая ошибки программирования!)инвариант, что a == f.a
и b == f.b
. В идеале я хотел бы иметь некоторый шаблон для Flow
, на который я могу передавать указатели членам с общим состоянием в качестве аргументов шаблона и который, в противном случае, действует так же, как Flow
. Следующий код довольно близко подходит к тому, что я хочу:
template<class T, Node* const T::* a, Node* const T::* b>
struct FlowLight{
int flow = 0;
public:
void setFlow(T* me, int newFlow){
int dif = newFlow - flow;
(me->*a)->excess -= dif;
(me->*b)->excess += dif;
flow = newFlow;
}
//Uh oh, we don't know the T* that owns this member in order to write the destructor!
};
Помимо проблем с деструктором, можно было бы написать
class UnrelatedObject{
//...
Node* const a;
Node* const b;
//...
FlowLight<UnrelatedObject, &UnrelatedObject::a, &UnrelatedObject::b> f;
//...
};
И это можно было бы сделать даже для работыполностью добавив функцию сигнатуры void cleanup(T*)
в шаблон и вызвав ее в деструкторе UnrelatedObject
, но это решение имеет неприятную привычку заставлять деструкторы записываться для классов, которые семантически не делают ничего особенного вразрушение (и оставляет место, чтобы забыть что-нибудь почистить и получить ошибки из-за этого!). Тем не менее, похоже, что должен быть способ, учитывая, что, как и в обычном классе, экземпляры такого шаблона знают, что члены данных находятся в некотором постоянном смещении от указателя this
- я просто не вижуочевидный способ передачи данных такого типа через систему типов во время компиляции.
Существует ли идиоматический способ решения проблемы такого рода, когда желаемые интерфейсы, такие как Flow
, несколько отсоединены от точногохранение данных?