当我尝试深度复制 `unique_ptr` 时出现段错误

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

为什么我不能 return 来自 clone 函数的 unique_ptr?我以为 I could do this.

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

我正在对这个 transform class 使用 unique_ptrs,所以我制作了一个(纯虚拟的)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 class 一次拥有其中的一些。毕竟,大多数统计模型都有不止一个参数。

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 不起作用。它用于制作副本,并从容器中访问单独的转换。当我 运行 一些测试时,我遇到了段错误。如果我 运行 它在 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[] 数组,在 all[= 上调用 clone() 45=] 个元素 - 甚至 add_transform() 尚未分配的元素!未分配的 unique_ptrs 将持有一个 nullptr 指针,它是 undefined behavior to call a non-static class method through a 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));
}