在 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));
请注意,移动操作后,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_ptr
的 vector
,即 可移动但不可复制(因为unique_ptr
可移动但不可复制)。因此,整个 State
class 是可移动的,但不可复制。因此,您必须 std::move
将 nstate
对象放入 states
向量中:
states.push_back(std::move(nstate));
如果你想要 copy 语义,你应该使用 shared_ptr
s 的向量(即 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
关键字的封装让你的代码更清晰,减少内存泄漏的可能性。
我有一个简单的 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));
请注意,移动操作后,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_ptr
的 vector
,即 可移动但不可复制(因为unique_ptr
可移动但不可复制)。因此,整个 State
class 是可移动的,但不可复制。因此,您必须 std::move
将 nstate
对象放入 states
向量中:
states.push_back(std::move(nstate));
如果你想要 copy 语义,你应该使用 shared_ptr
s 的向量(即 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
关键字的封装让你的代码更清晰,减少内存泄漏的可能性。