简体   繁体   中英

Resolving circular reference variable dependency in C++

Suppose i have following code for making a data flow architecture with C++

class DataflowClass {
    public:
        DataflowClass(int & _a, int & b) : a(_a), b(b){}

        inline void calculate(){
            c = a + b;
            d = a - 2*b;
        }

        inline int & getC() { return c; }
        inline int & getD() { return d; }

    private:
        int c = 1;
        int d = 2;
        int & a;
        int & b;
};

void TestReferenceVariableDataflow(int count){

    int a;
    int b;

    DataflowClass c1(a, b);
    DataflowClass c2(c1.getC(), c1.getD());
    DataflowClass c3(c2.getC(), c2.getD());

    for(int i=0;i<count;i++){
        a = c3.getC();
        b = c3.getD();
        c1.calculate();
        c2.calculate();
        c3.calculate();
    }
}

The DataflowClass is a main building for dataflow blocks which have two inputs and two outputs. The constructor of DataflowClass accepts two reference variables as inputs and has two getter functions which return the reference to its two output members, As can be seen, in TestReferenceVariableDataflow function i constructed 3 instances of DataflowClass, the c1 instance constructed with a reference to two local variables defined in TestReferenceVariableDataflow function, the c2 has been constructed with two references comming from c1.getC() and c2.getC() and vice versa for c3.

in each iteration, the calculate method of 3 classes will be called defined order, which result in dataflow computation within the 3 classes.

The real problem is circular dependency, as you can see, the c1 constructed with reference of two variables defined in TestReferenceVariableDataflow function and at the end of each iteration, i have to copy the values from c3.getC() and c3.getD() into those variables.

as the DataflowClass accepts reference variables in its constructor, i can't connect the output variables of c3 directly to c1, because the c3 instance isn't available at the construction time of c1 and so i have to define two extra variable to copy the values in each iteration from the last to the first.

as you know, the classes with reference members should initialize the reference at the construction time and this issue makes that circular dependency problem.

Is there any solution to this circular dependency problem?

Edit There are some simple reasons behind this design,

1- first of all, this is design only uses C++ generic syntax and not any other sort of libraries or even std::*, just because of performance, think there are tens of these blocks, and all connected to each other serially, another reason to avoid using libraries, is this code is part of real time software and its behavior is much more predictable than libraries

2- The instances of DataflowClass will never destroy and we can't have dangling pointers

3- In time critical and realtime softwares, dynamic memory management are somehow prohibited, because of unpredictability and this design truly could be used with all static memory allocation

4- Use of pointers are not considered as good solution, because with the pointers static compile time assertion can't be achieved, the compile time wiring prevents unconnected input nets Also the pointers may get null or something like that

5- this design doesn't need change signals for each variables, because all calculate methods will be called serially, and also maybe called automatically with a parent class

6- The inputs of blocks may come from different blocks (than their previous blocks), the provided example is just a simple case which the blocks connected to each other uniformly

7- Simple this design would be a C++ implementation of simulink blocks, the output of blocks are connected to the input of other blocks and they will calculate their outputs with increment of time

You might use pointer instead of references:

class DataflowClass {
public:
    DataflowClass() = default;

    DataflowClass(int& pa, int& pb) : a(&pa), b(&pb){}
    void linkTo(int& pa, int& pb) { a = &pa; b = &pb; }

    void calculate(){
        assert(a != nullptr && b != nullptr);
        c = *a + *b;
        d = *a - 2 * *b;
    }

    int & getC() { return c; }
    int & getD() { return d; }

private:
    int c = 1;
    int d = 2;
    const int* a = nullptr;
    const int* b = nullptr;

};

void TestReferenceVariableDataflow(int count)
{
    DataflowClass c1;
    DataflowClass c2(c1.getC(), c1.getD());
    DataflowClass c3(c2.getC(), c2.getD());
    c1.linkTo(c3.getC(), c3.getD());

    for(int i=0;i<count;i++){
        c1.calculate();
        c2.calculate();
        c3.calculate();
    }
}

but then pointer might be null :-/

Having something like the following seems simpler:

std::pair<int, int> calculate(int a, int b) { return { a + b, a - 2 * b} ; }

void TestReferenceVariableDataflow(int count)
{
    int a = 1;
    int b = 2;

    for(int i=0;i<count;i++){
        std::tie(a, b)  = calculate(a, b);
        std::tie(a, b)  = calculate(a, b);
        std::tie(a, b)  = calculate(a, b);
    }
}

The only way I can sensibly see this working with only references is by decoupling the storage entirely.

class DataFlowClass {
    const int& a;
    const int& b;
    int& c;
    int& d;

public:
    DataFlowClass(const int& a, const int& b, int& c, int& d) : a{a}, b{b}, c{c}, d{d} {
        c = 1;
        d = 2;
    }

    void calculate() {
        c = a + b;
        d = a - 2*b;
    }
};

void TestReferenceVariableDataflow(int count){
    int storage[6]; // could use 6 int variables instead

    DataflowClass c1(storage[0], storage[1], storage[2], storage[3]);
    DataflowClass c2(storage[2], storage[3], storage[4], storage[5]);
    DataflowClass c3(storage[4], storage[5], storage[0], storage[1]);

    for(int i=0;i<count;i++){
        c1.calculate();
        c2.calculate();
        c3.calculate();
    }
}

However, here it is the responsibility of the caller to ensure all references get setup correctly, which might get complicated.

Since you mentioned that this is targeting an embedded environment, note that this solution likely takes 1.5 times the amount of memory necessary for the pointer-based solution in Jarod42's answer .

Your design is overcomplicated without much reason:

class DataflowClass {
    public:
        void calculate(const DataflowClass &from ){
            c = from.getC() + from.getD();
            d = from.getC() - 2*from.getD();
        }

        int getC() const { return c; }
        int getD() const { return d; }

    private:
        int c = 1;
        int d = 2;
};

void TestReferenceVariableDataflow(int count){
    std::array<DataflowClass,3> c;

    for(int i=0;i<count;i++){
        for( size_t i = 0; i < c.size(); ++i ) {
            auto prev = ( i == 0 ? c.size() : i ) - 1;
            c[i].calculate( c[prev] ); 
        }
    }
}

this does the same more generic and safer way.

Finally found an optimal solution using std::reference_wrapper

class DataflowClass {
    public:
        DataflowClass(){}

        inline void setInputs(int & _a, int & _b){
            this->a = _a ;
            this->b = _b;
        }

        inline void calculate(){
            c = a + b;
            d = a - 2*b;
        }

        inline int & getC() { return c; }
        inline int & getD() { return d; }

    private:
        int c = 1;
        int d = 2;
        int a_initial = 0;
        int b_initial = 0;
        std::reference_wrapper<int> a = a_initial;
        std::reference_wrapper<int> b = b_initial;
};

void TestReferenceVariableDataflow(int count){

    DataflowClass c1, c2, c3;
    c2.setInputs(c1.getC(), c1.getD());
    c3.setInputs(c2.getC(), c2.getD());
    c1.setInputs(c3.getC(), c3.getD());

    for(int i=0;i<count;i++){
        c1.calculate();
        c2.calculate();
        c3.calculate();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM