当我尝试深度复制 `unique_ptr`s 编辑:

问题描述

为什么我不能从 unique_ptr 函数返回 clone?我以为I could do this

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

在这unique_ptr 类中使用了 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 不起作用。它用于制作副本,以及从容器访问单个转换。当我运行一些测试时,我得到了一个错误。如果我在 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_ptr 将持有一个 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,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));
}