【问题标题】:Pushing an object with unique_ptr into vector in C++在 C++ 中将具有 unique_ptr 的对象推送到向量中
【发布时间】:2017-11-07 06:42:35
【问题描述】:

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

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

我想这是由于向量制作了 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())));
        }
    }
}

【问题讨论】:

  • 添加移动构造函数,可以默认。

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


【解决方案1】:

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

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

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

LIVE


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

【讨论】:

  • 这无需创建默认移动构造函数即可工作
  • @StuartLacy 它是为State 隐式生成的。
  • 它并没有完全解决问题。在push_back 的末尾,nstate 将是空的(保持nullptr)并让操作只能通过取消引用他们希望不做的数组来访问状态。如果这是计划的话,在push_back 之前取一个指针State* pnstate=nstate.get()
  • 没错,我将push_back 作为循环中的最后一个语句,这样我就可以事先对nstate 做任何我需要做的事情。也许我的问题不够清楚
  • @Persixty 够公平的。我加了。
【解决方案2】:

您需要做出所有权决定。

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

如果vector&lt;&gt; 拥有该对象,则std::move()std::unique_ptr&lt;&gt; 放入vector 并继续通过“原始”指针访问该对象,但如果vector 被破坏或@ 将失效987654327@ 被擦除/重置。

如果vector&lt;&gt; 不拥有该对象,则将其声明为vector&lt;State*&gt; 并认识到当std::unique_ptr 被破坏时它将失效(除非您进行干预)。

如果存在复杂的关系,请考虑 std::shared_ptr&lt;&gt;,这将允许多个对象共享所有权,但确保不会发生循环引用。

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

肤浅的检查表明,一个“国家”可能拥有它的Transitions,因为总的来说,当国家存在时它们是有意义的,而当它不存在时它们就不再有意义。所以继续使用vector&lt;std::unique_ptr&lt;&gt; &gt; 并访问State 和/或Transition 作为指针。

如果这在您的情况下不起作用,您可能需要一个“FiniteState”上下文对象,它拥有所有状态和所有转换,并注意删除所有状态和所有关联的转换被销毁了。

【讨论】:

    【解决方案3】:
    // This line causes compiler errors
    states.push_back(nstate);
    

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

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

    如果你想要 copy 语义,你应该使用shared_ptrs 的向量(它们是引用计数智能指针,并且是两者 可复制和可移动)。


    我还会对您的 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 中引入)而不是使用显式调用 new 返回的原始指针来构造 std::unique_ptr

    【讨论】:

    • 令人讨厌的是,我坚持使用 C++11,但会记住这一点
    【解决方案4】:

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

    【讨论】:

    • C++17 起:auto&amp; nstate = states.emplace_back(state_index);
    【解决方案5】:

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

    【讨论】:

    • 代码在没有指定移动构造函数的情况下运行,将其声明为默认值只是为了明确还是起到其他作用?
    • @StuartLacy 在某些情况下,移动构造函数将由编译器隐式创建。在其他一些情况下,它不会,然后需要一个明确的默认值。以任何一种方式写下来都是一种很好的做法,并且可以作为文档
    【解决方案6】:

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

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

    您也可能想要使用std::make_unique&lt;Transition&gt;() 而不是std::uniqye_ptr&lt;Transition&gt;(new Transition())

    new 关键字的封装使您的代码更清晰,并降低了造成内存泄漏的可能性。

    【讨论】:

      猜你喜欢
      • 2020-03-25
      • 2014-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多