Pushing an object with unique_ptr into vector in C++

可紊 提交于 2021-02-18 10:40:09

问题


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())));
        }
    }
}

回答1:


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.




回答2:


You need to make an ownership decision.

The owner (or owners) of a new allocated object are responsible for ensuring it is 'delete'ed at the end of its lifecycle.

If the vector<> owns the object then std::move() the std::unique_ptr<> into the vector and continue to access the object through a 'raw' pointer but will be invalidated if the vector is destructed or the std::unique_ptr is erased/reset.

If the vector<> doesn't own the object than declare it vector<State*> and recognise it will be invalidated when the std::unique_ptr is destructed (unless you intervene).

If there's a complex relationship consider std::shared_ptr<> which will allow multiple objects to share ownership but make sure that circular referencing can't take place.

Beyond that you're into more complex ownership models and possible 'garbage collection'.

A superficial examination suggests a 'State' probably owns its Transitions because on the whole they make sense while the state exists and stop making sense when it doesn't. So continue with the vector<std::unique_ptr<> > and access State and/or Transition as a pointer.

If that doesn't work in your case, you probably need a 'FiniteState' context object that owns all the states and all the transitions and takes care to remove all the states and all the associated transitions from and to when a state is destroyed.




回答3:


You should avoid using push_back and use emplace_back to create items inplace 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{}});
    }
}



回答4:


You need to implement a move constructor for your 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));



回答5:


// 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.




回答6:


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

If you will pass value by reference std::unique_ptr<Transition>& trans, you wouldn't need any std::move outside add_transition function.

Also you may want to use std::make_unique<Transition>() instead of std::uniqye_ptr<Transition>(new Transition()).

Encapsulation of new keyword makes your code clearer and decreeses probability of creating memory leak.



来源:https://stackoverflow.com/questions/44386774/pushing-an-object-with-unique-ptr-into-vector-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!