Члены данных, которые делят состояние с классом - PullRequest
1 голос
/ 21 октября 2019

Я столкнулся с проблемой в нескольких контекстах, когда класс содержит объект, который, хотя и не связан каким-либо ужасно близким образом, разделяет значительную часть своего состояния с другим объектом. Это вызывает у меня некоторые проблемы, поскольку в результате я получаю объекты, которые в несколько раз больше, чем они должны быть из-за перекрывающегося состояния, и пару раз я сталкивался с ужасными ошибками из-за того, что не мог должным образом сохранять состояния, заблокированные вместе. Я не смог найти удовлетворительного ответа на эти вопросы, поэтому мне интересно, есть ли какие-либо парадигмы 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, несколько отсоединены от точногохранение данных?

...