Using std::vector, boost::variant, and types with reference fields together

910 views Asked by At

I have the following two classes:

struct A {

    A() : state(0) { }
    A(int state_arg) 
        : state{ state_arg } { }

    int state;
};

struct B {

    B(int state_arg, const int& ref)
        : state{ state_arg }, ref{ ref } { }

    int state;
    const int& ref;
};

I pretend the field ref in the second one to be a reference to an integer in another location, maybe (but not necessary) the field state of some instance of type B.

Now I want to performs some operations over those types, Indeed, I use the boost::variant library.

using my_type = boost::variant<A, B>;

Now when I work with variables of my_type all works as expected. For example:

int main() {

    my_type a(A(45));
    my_type b(B(45, boost::get<A>(a).state));

    A& at = boost::get<A>(a);
    B& bt = boost::get<B>(b);

    if (at.state == bt.ref) {
        std::cout << "AS EXPECTED" << std::endl;
    }
    // that prints "AS EXPECTED"
}

But when I work with a std::vector of my_type the things go wrong !

int main() {

    std::vector<my_type> vec;
    vec.push_back(A(45));
    vec.push_back(B(45, boost::get<A>(vec[0]).state));

    A& at = boost::get<A>(vec[0]);
    B& bt = boost::get<B>(vec[1]);

    if (at.state == bt.ref) {
        std::cout << "SHOULD I EXPECTED THIS ?" << std::endl;
    }
    // the code doesn't print
}

Now, I want to know what is going on here, i.e What is occurring that in the code above that the if condition evaluation gives false ?

And is possible, I would like to receive some advice in how to accomplish this tasks. Thanks in advance.

1

There are 1 answers

0
Slava On BEST ANSWER

Problem is that when you add second element to the vector, it reallocates more memory and moves first object to the new location and you have dangled reference. Simple solution would be to reserve enough memory in std::vector in advance to prevent reallocation or use another container that does not move objects. But your original solution has a design flaw - it relies on the fact that object it has reference to should outlive it. But your logic cannot guarantee that so it leads to the issue you see in the std::vector. Same issue could be in the first example if object b would outlive object a somehow. Better solution would be to use smart pointers and let objects of type B hold a shared or weak pointer to object of type A, depends on ownership you want to have. This way you will have shared pointers in std::vector and memory reallocation would not affect you:

struct A {

    A() : state(0) { }
    A(int state_arg) 
        : state{ state_arg } { }

    int state;
};
typedef std::shared_ptr<A> APtr;

struct B {

    B(int state_arg, const APtr& ptr)
        : state{ state_arg }, aptr{ ptr } { }

    int state;
    APtr aptr;
    int ref() const { return aptr->state; }
}
typedef std::shared_ptr<B> BPtr;

using my_type = boost::variant<APtr, BPtr>;