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())));
}
}
}
source to share
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));
Note that after the move operation, the data item nstate
(including the vector and its contents) will also be moved.
source to share
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 ...
source to share
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{}});
}
}
source to share
// 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
.
source to share
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.
source to share