Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pushing an object with unique_ptr into vector in C++

I have a simple class structure modelling a discrete simulation, with a vector of States, which each contain a number of Transitions, held as a vector of smart pointers. I've used smart pointers to hold the transitions as in my full application I need polymorphism.

#include <vector>
#include <memory>

class Transition {
    public:
        Transition() {}
};


class State {
    public:
        State(int num) : num(num), transitions() {}
        void add_transition(std::unique_ptr<Transition> trans) {
            transitions.push_back(std::move(trans));
        }

    private:
        int num;
        std::vector<std::unique_ptr<Transition>> transitions;
};


int main() {
    std::vector<State> states;
    for (int i = 0; i < 10; i++) {
        State nstate = State(i);
        for (int j = 0; j < 2; j++) {
            nstate.add_transition(std::move(std::unique_ptr<Transition>(new Transition())));
        }
        // This line causes compiler errors
        states.push_back(nstate);
    }
}

I get compiler errors when adding the new state object to the vector:

Error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Transition; _Dp = std::default_delete<Transition>]’
 { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }

I imagine this is due to vector making a copy of the State object which is also trying to make a copy of the vector of unique_ptrs which isn't allowed. I've seen that emplace_back doesn't make copies like push_back does but I still get the same error.

Adding the State object directly into the vector works, but I'd prefer to avoid this workaround as in my actual code I do more work with the State object rather than just adding transitions and don't want to keep accessing the vector.

int main() {
    std::vector<State> states;
    for (int i = 0; i < 10; i++) {
        states.push_back(State(i));
        for (int j = 0; j < 2; j++) {
            states[i].add_transition(std::move(std::unique_ptr<Transition>(new Transition())));
        }
    }
}
like image 813
Stuart Lacy Avatar asked Jun 06 '17 09:06

Stuart Lacy


2 Answers

State is not copyable, but only moveable; But for states.push_back(nstate);, nstate is an lvalue (as a named variable), which can't be moved from. Then a copy is tried to perform but it's not allowed.

To solve it you could use std::move (to turn it to an rvalue):

states.push_back(std::move(nstate));

LIVE


Note that after the move operation, the data member of nstate (including the vector and its content) will be moved too.

like image 111
songyuanyao Avatar answered Sep 20 '22 14:09

songyuanyao


// This line causes compiler errors
states.push_back(nstate);

The nstate object is an instance of the State class. The State class contains two data members: an int (which is copyable), and a vector of unique_ptrs, that is movable but not copyable (since unique_ptr is movable but not copyable). As a result of that, the whole State class is movable but not copyable. So, you must std::move the nstate object into the states vector:

states.push_back(std::move(nstate));

If you want copy semantics, you should use a vector of shared_ptrs (that are reference counted smart pointers, and are both copyable and movable).


I would do also a few modifications to your State class code:

class State {
    public:
        State(int num) : num(num), transitions() {}

Here, you should mark the constructor explicit, to avoid implicit conversions from int. Moreover, the std::vector data member is automatically initialized, no need to use transitions() here.

Moreover, considering this line of code:

states[i].add_transition(std::move(std::unique_ptr<Transition>(new Transition())));

you should to use std::make_unique (introduced in C++14) instead of constructing a std::unique_ptr with the raw pointer returned by an explicit call to new.

like image 32
Mr.C64 Avatar answered Sep 22 '22 14:09

Mr.C64