简体   繁体   English

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

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

Background: this is a followup to @pari's answer to Constant-sized vector .背景:这是@pari 对Constant-sized vector的回答的后续。

My type, metadata_t does not have a default constructor.我的类型metadata_t没有默认构造函数。

I use std::make_unique for fixed-size arrays that the size is not available in compile-time.我将std::make_unique用于固定大小的数组,其大小在编译时不可用。 (ie const size). (即常量大小)。

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
}

However, the constructor of unique_ptr<...[]> only accepts a size (integer).但是, unique_ptr<...[]>的构造函数只接受一个大小(整数)。 How do I initialise and copy my vector into my unique_ptr<[]> ?如何初始化向量并将其复制到我的unique_ptr<[]>中?

I tried std::unique_ptr<metadata_t[]>(array.size());我试过std::unique_ptr<metadata_t[]>(array.size()); to prepare and then copy/populate the contents in the next step, but it shows a compile error.准备然后在下一步中复制/填充内容,但它显示编译错误。

Note: I use C++20 (or higher if it was available).注意:我使用 C++20(或更高版本,如果可用)。 Could make_unique_for_overwrite be useful? make_unique_for_overwrite有用吗? ( C++23 ). (C++23)。

Note: At first I thought generate (as in this answer ) can do it, but it does not solve my problem because n is a run-time information.注意:起初我认为generate (如在这个答案中)可以做到,但它不能解决我的问题,因为n是运行时信息。

The size of my vector is determined at run-time.我的向量的大小是在运行时确定的。

The whole point of this function is to convert my std::vector into a fixed-size data structure (with run-time size).这个函数的重点是将我的std::vector转换为固定大小的数据结构(具有运行时大小)。

The data structure does not have to be unique_ptr<T[]> .数据结构不必是unique_ptr<T[]> The old title referred to unique_ptr but I am really looking for a solution for a fixed-size data structure.旧标题提到了unique_ptr ,但我真的在寻找固定大小数据结构的解决方案。 So far, it is the only data structure I found as a "constant-size indexed container with size defined at runtime".到目前为止,它是我发现的唯一一个作为“在运行时定义大小的恒定大小索引容器”的数据结构。

You can't initialize the elements of a unique_ptr<T[]> array with the elements of a vector<T> array when constructing the new array (UPDATE: apparently you can , but it is still not going to be a solution solved with a single statement, as you are trying to do).构造新数组时,您无法使用vector<T>数组的元素初始化unique_ptr<T[]>数组的元素(更新:显然可以,但它仍然不是解决方案一个单一的声明,就像你试图做的那样)。

You will have to allocate the T[] array first, and then copy the vector 's elements into that array one at a time, eg:您必须先分配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;
}

UPDATE: you updated your question to say that metadata_t does not have a default constructor.更新:您更新了您的问题,说metadata_t没有默认构造函数。 Well, that greatly complicates your situation.好吧,这使您的情况变得非常复杂。

The only way to create an array of objects that don't support default construction is to allocate an array of raw bytes of sufficient size and then use placement-new to construct the individual objects within those bytes.创建不支持默认构造的对象数组的唯一方法是分配一个足够大小的原始字节数组,然后使用placement-new在这些字节中构造单个对象。 But now, you are having to manage not only the objects in the array, but also the byte array itself.但是现在,您不仅要管理数组中的对象,还要管理字节数组本身。 By itself, unique_ptr<T[]> won't be able to free that byte array, so you would have to provide it with a custom deleter that frees the objects and the byte array. unique_ptr<T[]>本身无法释放该字节数组,因此您必须为其提供一个自定义deleter来释放对象字节数组。 Which also means, you will have to keep track of how many objects are in the array (something new[] does for you so delete[] works, but you can't access that counter, so you will need your own), eg:这也意味着,您必须跟踪数组中有多少对象( 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);
}

Needless to say, this puts much more responsibility on you to allocate and free memory correctly, and it is really just not worth the effort at this point.不用说,这让您承担了更多正确分配和释放内存的责任,而且在这一点上真的不值得付出努力。 std::vector already supports everything you need. std::vector已经支持你需要的一切。 It can create an object array using a size known at runtime, and it can create non-default-constructable objects in that array, eg.它可以使用运行时已知的大小创建对象数组,并且可以在该数组中创建不可默认构造的对象,例如。

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;
}

Which is really just a less-efficient way of avoiding the vector 's own copy constructor:这实际上只是避免vector自己的复制构造函数的一种效率较低的方法:

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

The data structure does not have to be unique_ptr<T[]> .数据结构不必是unique_ptr<T[]> The old title referred to unique_ptr but I am really looking for a solution for a fixed-size data structure.旧标题提到了unique_ptr ,但我真的在寻找固定大小数据结构的解决方案。 So far, it is the only data structure I found as a "constant-size indexed container with size defined at runtime".到目前为止,它是我发现的唯一一个作为“在运行时定义大小的恒定大小索引容器”的数据结构。

What you are looking for is exactly what std::vector already gives you.您正在寻找的正是std::vector已经为您提供的。 You just don't seem to realize it, or want to accept it.你只是似乎没有意识到它,或者不想接受它。 Both std::unique_ptr<T[]> and std::vector<T> hold a pointer to a dynamically-allocated array of a fixed size specified at runtime. std::unique_ptr<T[]>std::vector<T>都保存一个指向在运行时指定的固定大小的动态分配数组的指针。 It is just that std::vector offers more functionality than std::unique_ptr<T[]> does to manage that array (for instance, re-allocating the array to a different size).只是std::vector提供了比std::unique_ptr<T[]>更多的功能来管理该数组(例如,将数组重新分配到不同的大小)。 You don't have to use that extra functionality if you don't need it, but its base functionality will suit your needs just fine.如果您不需要它,则不必使用该额外功能,但它的基本功能将很好地满足您的需求。

Initializing an array of non-default constructibles from a vector is tricky.vector初始化一组非默认构造物是很棘手的。

One way, if you know that your vector will never contain more than a certain amount of elements, could be to create an index_sequence covering all elements in the vector .一种方法,如果你知道你的vector永远不会包含超过一定数量的元素,可能是创建一个index_sequence覆盖vector中的所有元素。 There will be one instantiation of the template for each number of elements in your vector that you plan to support and the compilation time will be "silly".您计划支持的向量中的每个元素数量都会有一个模板实例化,并且编译时间将是“愚蠢的”。

Here I've selected the limit 512. It must have a limit, or else the compiler will spin in endless recursion until it gives up or crashes.这里我选择了限制 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>{});
}

You can then turn your vector into a std::unique_ptr<metadata_t[]> :然后,您可以将vector转换为std::unique_ptr<metadata_t[]>

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

Demo (compilation time may exceed the time limit) Demo (编译时间可能超过时限)

You have to allocate some uninitialized memory for an array and copy construct the elements in-place using construct_at .您必须为数组分配一些未初始化的内存并使用construct_at就地复制构造元素。 You can then create a unique_ptr using the address of the constructed array:然后,您可以使用构造数组的地址创建一个 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);
}

You can allocate raw memory, copy (or move) construct your data there, and store the result in a unique_ptr.您可以分配原始内存,复制(或移动)在那里构建您的数据,并将结果存储在 unique_ptr 中。 I'm not dealing with exception safety if your copy constructor throws.如果您的复制构造函数抛出,我不会处理异常安全。

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