在 C++ 中将带有 unique_ptr 的对象推入 vector

Pushing an object with unique_ptr into vector in C++

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

#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 对象的副本,而 State 对象也在尝试制作 unique_ptrs 的 vector 的副本,这是不允许的。我已经看到 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())));
        }
    }
}

State不可复制,只能移动;但是对于 states.push_back(nstate);nstate 是一个左值(作为命名变量),不能从中移动。然后尝试执行副本,但不允许。

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

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

LIVE


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

您需要为您的 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));

您需要做出所有权决定。

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

如果 vector<> 拥有该对象,则 std::move()std::unique_ptr<> 放入 vector 并继续通过 'raw' 指针访问该对象,但将如果 vector 被破坏或者 std::unique_ptr 是 erased/reset.

则无效

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

如果关系复杂,请考虑 std::shared_ptr<>,这将允许多个对象共享所有权,但要确保不会发生循环引用。

除此之外,您还会遇到更复杂的所有权模型和可能 'garbage collection'。

粗略的检查表明 'State' 可能拥有它的 Transition,因为总的来说,它们在状态存在时有意义,而在状态不存在时就不再有意义。所以继续 vector<std::unique_ptr<> > 并访问 State and/or Transition 作为指针。

如果这在您的情况下不起作用,您可能需要一个 'FiniteState' 上下文对象,它拥有所有状态和所有转换,并注意从和中删除所有状态和所有关联的转换当一个国家被摧毁时。

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

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

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

如果你想要 copy 语义,你应该使用 shared_ptrs 的向量(即 reference counted 智能指针, 并且 可复制和可移动)。


我也会对您的 State class 代码做一些修改:

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 中引入),而不是使用显式调用 new.[=37 返回的原始指针构造 std::unique_ptr =]

您应该避免使用 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{}});
    }
}

您正在将 std::unique_ptr<Transition> 传递给按值函数,这应该会在 void add_transition(std::unique_ptr<Transition> trans) 中创建一个本地副本。

如果您将通过引用传递值 std::unique_ptr<Transition>& trans,则在 add_transition 函数之外不需要任何 std::move

您可能还想使用 std::make_unique<Transition>() 而不是 std::uniqye_ptr<Transition>(new Transition())

new关键字的封装让你的代码更清晰,减少内存泄漏的可能性。