Pushing a unique_ptr object to a vector in C ++

I have a simple class structure simulating discrete simulation, with a vector of states, each containing a series of transitions, which are stored as a vector of smart pointers. I have used smart pointers to store the transitions since I need polymorphism in my full application.

#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 am getting compiler errors when adding a new state object to a 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 guess this is because the vector is creating a copy of the State object, which is also trying to make a copy of the vector unique_ptrs

, which is not permitted. I saw that emplace_back

it doesn't make copies of the type push_back

, but I still get the same error.

Adding the State object directly to the vector works, but I would rather avoid this workaround like in my actual code. I am working more with the State object rather than just adding transitions and don't want to continue to access 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())));
        }
    }
}

      

+3


source to share


6 answers


State

not copied, but only moves; But for states.push_back(nstate);

, nstate

is an lvalue (like a named variable) from which it is not possible to transfer. Then a copy is attempted, but this is not permitted.

To solve this problem, you can use std::move

(to turn it into an rvalue):

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

      



LIVE


Note that after the move operation, the data item nstate

(including the vector and its contents) will also be moved.

+3


source


You need to decide on ownership.

The owner (or s) of the allocated object new

is responsible for being "removed" at the end of its life cycle.

If vector<>

owns an object std::move()

std::unique_ptr<>

in vector

and continues to access the object via a 'raw' pointer, but will be invalid if vector

destroyed or std::unique_ptr

erased / reset.

If it vector<>

does not belong to this object, than it declares it vector<State*>

and recognizes that it will be invalidated if std::unique_ptr

destroyed (if you don't interfere).



If there is a complex relationship, consider std::shared_ptr<>

one that allows multiple objects to share ownership, but make sure no circular references can take place.

In addition, you use more complex ownership models and possible garbage collection.

A cursory examination suggests that "the state" probably owns it Transition

, because in general they make sense as long as the state exists and ceases to make sense when it does not. So, go ahead with vector<std::unique_ptr<> >

and access State

and / or Transition

as a pointer.

If that doesn't work for your case, you probably need a "FiniteState" context object that owns all states and all transitions and takes care of removing all states and all associated transitions from and to the point where the state is destroyed ...

+3


source


You should avoid using push_back

and use emplace_back

to create elements instead.

constexpr ::std::int32_t const states_count{10};
constexpr ::std::int32_t const transitions_per_state_count{2};
::std::vector< State > states;
states.reserve(states_count);
for(::std::int32_t state_index{}; states_count != state_index; ++state_index)
{
    states.emplace_back(state_index); // new state is added without copying or moving anything
    auto & nstate{states.back()};
    for(::std::int32_t transition_index{}; transitions_per_state_count != transition_index; ++transition_index)
    {
        nstate.add_transition(::std::unique_ptr< Transition >{new Transition{}});
    }
}

      

+2


source


You need to implement the move constructor for State

and call std::move

to move the object

class State {
public:
    // default if you just want it to move the members one by one
    State(State&& s) = default;
};

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

      

+1


source


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

      

An object nstate

is an instance of a class State

. The class State

contains two data items: int

(which is copyable) and vector

of unique_ptr

s, which is movable but not copyable (because it unique_ptr

is movable but not copied). As a result, the entire class State

is movable, but not copyable. So, you have to std::move

object nstate

to vector states

:

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

      

If you want to use copy semantics, you must use a vector shared_ptr

(which counts as counted smart pointers and both are copyable and movable).


I would make a few modifications to your class code State

:

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

      

This is where you must mark the constructor explicit

to avoid implicit conversions from int

. Also, the item is std::vector

automatically initialized, no need to use transitions()

here.

Also, given this line of code:

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

      

you should use std::make_unique

(introduced in C ++ 14) instead of constructing std::unique_ptr

with a raw pointer returned by an explicit call new

.

+1


source


You are passing a std::unique_ptr<Transition>

function by value, which should create a local copy in void add_transition(std::unique_ptr<Transition> trans)

.

If you pass the value by reference std::unique_ptr<Transition>& trans

, you don't need the function std::move

outside add_transition

.

Also you can use std::make_unique<Transition>()

instead std::uniqye_ptr<Transition>(new Transition())

.

Keyword encapsulation new

makes your code clearer and determines the likelihood of a memory leak.

+1


source







All Articles