当我尝试深度复制`unique_ptr`时出现segfault

getting a segfault when I try to deep copy `unique_ptr`s

本文关键字:ptr segfault unique 深度 复制      更新时间:2024-05-10

为什么我不能从clone函数返回unique_ptr?我以为我能做到。

我有一个用于不同数学函数的基类,叫做transform。我有一个指向这种类型的指针容器,因为我使用的是多态性。例如,所有这些派生类都有log_jacobian的不同实现,这对统计算法很有用。

我将unique_ptrs用于这个transform类,因此我制作了一个(纯虚拟(clone函数,使新的unique_ptr指向同一数学transform对象的深层副本。这个新对象的类型与从transform<float_t>派生的对象的类型相同,但它是单独的,因为不能有两个unique_ptr指向同一个对象。

template<typename float_t>
class transform{
...
virtual std::unique_ptr<transform<float_t>> clone() const = 0;
...
};

我的transform_container课一次只上几个这样的课。毕竟,大多数统计模型都有不止一个参数。

template<typename float_t, size_t numelem>
class transform_container{
private:
using array_ptrs = std::array<std::unique_ptr<transform<float_t>>, numelem>;
array_ptrs m_ts;
unsigned m_add_idx;
...
auto get_transforms() const -> array_ptrs;
};

不过,我不知道为什么深度复制函数get_transforms不起作用。它用于制作副本,并访问容器中的各个转换。当我运行一些测试时,我会出现segfault。如果我在gdb中运行它,它会明确地"告诉"我这行在它坏了之后有一个注释。

template<typename float_t, size_t numelem>
auto transform_container<float_t,numelem>::get_transforms() const -> array_ptrs
{
array_ptrs deep_cpy;
for(size_t i = 0; i < numelem; ++i){
deep_cpy[i] = m_ts[i]->clone(); // this line
}
return deep_cpy;
}

我还尝试过将std::move转换为deep_cpy[i]并使用unique_ptr::reset,但没有成功。

编辑:

这里还有一些其他相关的方法:向transform_container添加转换的方法,以及单个transform:的工厂方法

template<typename float_t>
std::unique_ptr<transform<float_t> > transform<float_t>::create(trans_type tt)
{
if(tt == trans_type::TT_null){

return std::unique_ptr<transform<float_t> >(new null_trans<float_t> );

}else if(tt == trans_type::TT_twice_fisher){

return std::unique_ptr<transform<float_t> >(new twice_fisher_trans<float_t> );

}else if(tt == trans_type::TT_logit){

return std::unique_ptr<transform<float_t> >(new logit_trans<float_t> );

}else if(tt == trans_type::TT_log){
return std::unique_ptr<transform<float_t> >(new log_trans<float_t> );

}else{
throw std::invalid_argument("that transform type was not accounted for");

}
}
template<typename float_t, size_t numelem>
void transform_container<float_t, numelem>::add_transform(trans_type tt)
{
m_ts[m_add_idx] = transform<float_t>::create(tt);
m_add_idx++;
}

get_transforms()中,您正在整个m_ts[]数组上循环,在所有元素上调用clone(),甚至是尚未由add_transform()分配的元素!未分配的unique_ptrs将持有一个nullptr指针,通过nullptr调用非静态类方法是未定义的行为

最简单的修复方法是将get_transforms()中的循环更改为使用m_add_idx而不是numelem:

template<typename float_t, size_t numelem>
auto transform_container<float_t,numelem>::get_transforms() const -> array_ptrs
{
array_ptrs deep_cpy;
for(size_t i = 0; i < m_add_idx; ++i){ // <-- here
deep_cpy[i] = m_ts[i]->clone();
}
return deep_cpy;
}

否则,您将不得不手动忽略任何nullptr元素,例如:

template<typename float_t, size_t numelem>
auto transform_container<float_t,numelem>::get_transforms() const -> array_ptrs
{
array_ptrs deep_cpy;
for(size_t i = 0, j = 0; i < numelem; ++i){
if (m_ts[i]) {
deep_cpy[j++] = m_ts[i]->clone();
}
}
return deep_cpy;
}

无论哪种方式,您都应该更新add_transform()以验证m_add_idx不会超过numelem:

template<typename float_t, size_t numelem>
void transform_container<float_t, numelem>::add_transform(trans_type tt)
{
if (m_add_idx >= numelem) throw std::length_error("cant add any more transforms"); // <-- here
m_ts[m_add_idx] = transform<float_t>::create(tt);
++m_add_idx;
}

也就是说,由于transform_container可以分配可变数量的变换,我建议将transform_container更改为使用std::vector而不是std::array,例如:

template<typename float_t>
class transform_container{
private:
using vector_ptrs = std::vector<std::unique_ptr<transform<float_t>>>;
vector_ptrs m_ts;
...
auto get_transforms() const -> vector_ptrs;
};
template<typename float_t>
auto transform_container<float_t>::get_transforms() const -> vector_ptrs
{
vector_ptrs deep_cpy;
deep_cpy.reserve(m_ts.size());
for(const auto &elem : m_ts){
deep_cpy.push_back(elem->clone());
}
return deep_cpy;
}
template<typename float_t>
std::unique_ptr<transform<float_t>> transform<float_t>::create(trans_type tt)
{
switch (tt) {
case trans_type::TT_null:
return std::make_unique<null_trans<float_t>>();
case trans_type::TT_twice_fisher:
return std::make_unique<twice_fisher_trans<float_t>>();

case trans_type::TT_logit:
return std::make_unique<logit_trans<float_t>>();

case trans_type::TT_log:
return std::make_unique<log_trans<float_t>>();
}    
throw std::invalid_argument("that transform type was not accounted for");
}
template<typename float_t>
void transform_container<float_t>::add_transform(trans_type tt)
{
m_ts.push_back(transform<float_t>::create(tt));
}