繁体   English   中英

如何从具有在运行时定义的大小的向量中填充具有固定大小的 C++ 容器?

[英]How to fill in a C++ container with fixed-size from vector with size defined at run-time?

背景:这是@pari 对Constant-sized vector的回答的后续。

我的类型metadata_t没有默认构造函数。

我将std::make_unique用于固定大小的数组,其大小在编译时不可用。 (即常量大小)。

typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t;
fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) {

    // note: this is run-time:
    auto n = array.size();

    return fixedsize_side_metadata_t(array.begin(), array.end());  // error
    return fixedsize_side_metadata_t(array);  // error
    return std::unique_ptr<metadata_t[]>(0); // no error, but not useful
    return std::unique_ptr<metadata_t[]>(n); // error
}

但是, unique_ptr<...[]>的构造函数只接受一个大小(整数)。 如何初始化向量并将其复制到我的unique_ptr<[]>中?

我试过std::unique_ptr<metadata_t[]>(array.size()); 准备然后在下一步中复制/填充内容,但它显示编译错误。

注意:我使用 C++20(或更高版本,如果可用)。 make_unique_for_overwrite有用吗? (C++23)。

注意:起初我认为generate (如在这个答案中)可以做到,但它不能解决我的问题,因为n是运行时信息。

我的向量的大小是在运行时确定的。

这个函数的重点是将我的std::vector转换为固定大小的数据结构(具有运行时大小)。

数据结构不必是unique_ptr<T[]> 旧标题提到了unique_ptr ,但我真的在寻找固定大小数据结构的解决方案。 到目前为止,它是我发现的唯一一个作为“在运行时定义大小的恒定大小索引容器”的数据结构。

构造新数组时,您无法使用vector<T>数组的元素初始化unique_ptr<T[]>数组的元素(更新:显然可以,但它仍然不是解决方案一个单一的声明,就像你试图做的那样)。

您必须先分配T[]数组,然后一次将vector的元素复制到该数组中,例如:

typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t;

fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) {
    fixedsize_metadata_t result = std::make_unique<metadata_t[]>(array.size());
    std::copy(array.begin(), array.end(), result.get());
    return result;
}

更新:您更新了您的问题,说metadata_t没有默认构造函数。 好吧,这使您的情况变得非常复杂。

创建不支持默认构造的对象数组的唯一方法是分配一个足够大小的原始字节数组,然后使用placement-new在这些字节中构造单个对象。 但是现在,您不仅要管理数组中的对象,还要管理字节数组本身。 unique_ptr<T[]>本身无法释放该字节数组,因此您必须为其提供一个自定义deleter来释放对象字节数组。 这也意味着,您必须跟踪数组中有多少对象( new[]为您做了一些事情,因此delete[]有效,但您无法访问该计数器,因此您需要自己的),例如:

struct metadata_arr_deleter
{
    void operator()(metadata_t *arr){
        size_t count = *(reinterpret_cast<size_t*>(arr)-1);
        for (size_t i = 0; i < count; ++i) {
            arr[i]->~metadata_t();
        }
        delete[] reinterpret_cast<char*>(arr);
    }
};

typedef std::unique_ptr<metadata_t[], metadata_arr_deleter> fixedsize_metadata_t;

fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) {

    const auto n = array.size();
    const size_t element_size = sizeof(std::aligned_storage_t<sizeof(metadata_t), alignof(metadata_t)>);

    auto raw_bytes = std::make_unique<char[]>(sizeof(size_t) + (n * element_size));

    size_t *ptr = reinterpret_cast<size_t*>(raw_bytes.get());
    *ptr++ = n;
    auto *uarr = reinterpret_cast<metadata_t*>(ptr);
    size_t i = 0;

    try {
        for (i = 0; i < n; ++i) {
            new (&uarr[i]) metadata_t(array[i]);
        }
    }
    catch (...) {
        for (size_t j = 0; j < i; ++j) {
            uarr[j]->~metadata_t();
        }
        throw;
    }

    raw_bytes.release();
    return fixedsize_metadata_t(uarr);
}

不用说,这让您承担了更多正确分配和释放内存的责任,而且在这一点上真的不值得付出努力。 std::vector已经支持你需要的一切。 它可以使用运行时已知的大小创建对象数组,并且可以在该数组中创建不可默认构造的对象,例如。

std::vector<metadata_t> consolidate(const std::vector<metadata_t> &array) {

    auto n = array.size();

    std::vector<metadata_t> varr;
    varr.reserve(n);

    for (const auto &elem : array) {
        // either:
        varr.push_back(elem);
        // or:
        varr.emplace_back(elem);
        // it doesn't really matter in this case, since they
        // will both copy-construct the new element in the array
        // from the current element being iterated...
    }

    return varr;
}

这实际上只是避免vector自己的复制构造函数的一种效率较低的方法:

std::vector<metadata_t> consolidate(const std::vector<metadata_t> &array) {
    return array; // will return a new copy of the array
}

数据结构不必是unique_ptr<T[]> 旧标题提到了unique_ptr ,但我真的在寻找固定大小数据结构的解决方案。 到目前为止,它是我发现的唯一一个作为“在运行时定义大小的恒定大小索引容器”的数据结构。

您正在寻找的正是std::vector已经为您提供的。 你只是似乎没有意识到它,或者不想接受它。 std::unique_ptr<T[]>std::vector<T>都保存一个指向在运行时指定的固定大小的动态分配数组的指针。 只是std::vector提供了比std::unique_ptr<T[]>更多的功能来管理该数组(例如,将数组重新分配到不同的大小)。 如果您不需要它,则不必使用该额外功能,但它的基本功能将很好地满足您的需求。

vector初始化一组非默认构造物是很棘手的。

一种方法,如果你知道你的vector永远不会包含超过一定数量的元素,可能是创建一个index_sequence覆盖vector中的所有元素。 您计划支持的向量中的每个元素数量都会有一个模板实例化,并且编译时间将是“愚蠢的”。

这里我选择了限制 512。它必须有一个限制,否则编译器将无限递归地旋转,直到它放弃或崩溃。

namespace detail {
template <class T, size_t... I>
auto helper(const std::vector<T>& v, std::index_sequence<I...>) {
    if constexpr (sizeof...(I) > 512) { // max 512 elements in the vector.
        return std::unique_ptr<T[]>{};  // return empty unique_ptr or throw
    } else {
        // some shortcuts to limit the depth of the call stack
        if(sizeof...(I)+255 < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+256>{});
        if(sizeof...(I)+127 < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+128>{});
        if(sizeof...(I)+63 < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+64>{});
        if(sizeof...(I)+31 < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+32>{});
        if(sizeof...(I)+15 < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+16>{});
        if(sizeof...(I)+7 < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+8>{});
        if(sizeof...(I)+3 < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+4>{});
        if(sizeof...(I)+1 < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+2>{});
        if(sizeof...(I) < v.size())
            return helper(v, std::make_index_sequence<sizeof...(I)+1>{});

        // sizeof...(I) == v.size(), create the pointer:
        return std::unique_ptr<T[]>(new T[sizeof...(I)]{v[I]...});
    }
}
} // namespace detail

template <class T>
auto make_unique_from_vector(const std::vector<T>& v) {
    return detail::helper(v, std::make_index_sequence<0>{});
}

然后,您可以将vector转换为std::unique_ptr<metadata_t[]>

auto up = make_unique_from_vector(foos);
if(up) {
    // all is good
}

Demo (编译时间可能超过时限)

您必须为数组分配一些未初始化的内存并使用construct_at就地复制构造元素。 然后,您可以使用构造数组的地址创建一个 unique_ptr:

#include <vector>
#include <memory>

struct metadata_t {
    metadata_t(int) { }
};

typedef std::unique_ptr<metadata_t[]> fixedsize_metadata_t;

fixedsize_metadata_t consolidate(const std::vector<metadata_t> &array) {
    // note: this is run-time:
    auto n = array.size();
    std::allocator<metadata_t> alloc;
    metadata_t *t = alloc.allocate(n);
    for (std::size_t i = 0; i < array.size(); ++i) {
        std::construct_at(&t[i], array[i]);
    }
    return fixedsize_metadata_t(t);
}

您可以分配原始内存,复制(或移动)在那里构建您的数据,并将结果存储在 unique_ptr 中。 如果您的复制构造函数抛出,我不会处理异常安全。

metadata_t* storage = static_cast<metadata_t*>(malloc(array.size() * sizeof(metadata_t)));
for (size_t ii = 0; ii < array.size(); ++ii)
    new (&storage[ii]) metadata_t(array[ii]); // copy construct
return std::unique_ptr<metadata_t[]>(storage);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM