使用unique_ptr将对象推入C ++中的向量

时间:2017-06-06 09:52:19

标签: c++ c++11 vector smart-pointers move-semantics

我有一个简单的类结构建模离散模拟,带有状态向量,每个状态包含许多过渡,作为智能指针的向量。我已经使用智能指针来保存转换,就像在我的完整应用程序中我需要多态性一样。

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

将新状态对象添加到向量时出现编译器错误:

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

我想这是因为vector制作了State对象的副本,它也试图制作unique_ptrs向量的副本,这是不允许的。我已经看到emplace_back没有制作像push_back这样的副本,但我仍然会遇到同样的错误。

将State对象直接添加到向量中,但是我更愿意避免这种解决方法,因为在我的实际代码中我更多地使用State对象而不是仅仅添加转换而不想继续访问向量

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

6 个答案:

答案 0 :(得分:4)

State不可复制,但只能移动;但对于states.push_back(nstate);nstate是左值(作为命名变量),无法移动。然后尝试复制,但不允许复制。

要解决此问题,您可以使用std::move(将其转换为右值):

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

LIVE

请注意,在移动操作之后,nstate的数据成员(包括向量及其内容)也将被移动。

答案 1 :(得分:3)

您需要做出所有权决定。

new已分配对象的所有者(或所有者)有责任确保在其生命周期结束时将其“删除”。

如果vector<>拥有该对象,则std::move() std::unique_ptr<>进入vector并继续通过'原始'指针访问该对象,但如果<{1}}被破坏或vector被删除/重置。

如果std::unique_ptr不拥有该对象而不是声明它vector<>,并且认识到vector<State*>被破坏时将会失效(除非您进行干预)。

如果存在复杂关系,请考虑std::unique_ptr,这将允许多个对象共享所有权,但确保不能进行循环引用。

除此之外,您将进入更复杂的所有权模型和可能的“垃圾收集”。

表面检查表明'州'可能拥有其std::shared_ptr<> s,因为总的来说,它们在国家存在时是有意义的,而当它不存在时就停止有意义。继续使用Transition并访问vector<std::unique_ptr<> >和/或State作为指针。

如果在你的情况下不起作用,你可能需要一个拥有所有状态和所有转换的'FiniteState'上下文对象,并注意从状态和状态中删除所有状态和所有相关的转换。被毁了。

答案 2 :(得分:2)

您应该避免使用push_back并使用emplace_back来代替地创建项目。

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

答案 3 :(得分:1)

您需要为State实施移动构造函数,并调用std::move移动对象

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

答案 4 :(得分:1)

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

nstate对象是State类的实例。 State类包含两个数据成员:int(可复制)和vectorunique_ptr可移动但不可复制(因为unique_ptr是可移动但不可复制的)。因此,整个State类是可移动的,但不可复制。因此,您必须std::movenstate对象放入states向量:

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

如果你想要 copy 语义,你应该使用shared_ptr s的向量(引用计数智能指针,两者< / em>可复制和移动)。

我还会对您的State类代码进行一些修改:

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

在这里,您应该标记构造函数explicit,以避免来自int隐式转换。此外,std::vector数据成员会自动初始化,无需在此处使用transitions()

此外,考虑到这行代码:

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

你应该使用std::make_unique(在C ++ 14中引入),而不是使用显式调用std::unique_ptr返回的原始指针构造new

答案 5 :(得分:1)

您正在将std::unique_ptr<Transition>传递给按值运行,该值应在void add_transition(std::unique_ptr<Transition> trans)中创建本地副本。

如果您通过引用std::unique_ptr<Transition>& trans传递值,则不需要任何std::moveadd_transition函数。

此外,您可能希望使用std::make_unique<Transition>()代替std::uniqye_ptr<Transition>(new Transition())

new关键字的封装使您的代码更加清晰,并判断创建内存泄漏的可能性。