【问题标题】:Best strategy for initializing static arrays in a struct in C++在 C++ 结构中初始化静态数组的最佳策略
【发布时间】:2021-05-13 21:50:17
【问题描述】:

假设结构体提供了两个模板参数,一个是typename T,另一个是size_t N。现在,该结构应该存储元素类型为T 和大小为N 的静态数组。在某些情况下,在创建实例时使用默认值初始化数组可能会很好,如果你有类似的东西,我认为无论如何都会发生这种情况

template<typename T, size_t N>
struct Foo {
    T values[N];
    size_t size;
    
    explicit Foo(size_t _size) : size{ _size } {}
}

所以,如前所述,我认为这种情况下的行为是values 只是自动使用默认值初始化(调用T 的默认构造函数)。但是,如果我想在构造对象时为数组传递一些值怎么办?这里的目标是我能够将一个静态数组传递给构造函数,并让该静态数组代替values。理想情况下,整个过程中只有两个数组创建。但这可能吗?考虑以下示例:

template<typename T, size_t N>
struct Foo {
    T values[N];
    size_t size;
    
    explicit Foo(T _values[N], size_t _size) : values{ _values }, size{ _size} {}
}

现在,除了我什至不知道上述是否会按预期工作之外,我仍然有点不确定何时在 C++ 中发生复制。我想在最坏的情况下,调用Foo的构造函数时会创建4个数组:

  1. 调用Foo的构造函数需要传入一个数组,所以需要事先创建。
  2. 构造函数参数_values是传值方式,所以这里发生了拷贝。
  3. 在构造函数初始化任何值之前,静态数组已经初始化(?)
  4. 将 _values 分配给 values 时,正在发生另一个副本 (?)

现在正如我所说,我真的不确定 C++ 中的复制行为。当然,我们可以通过使_values 参数按引用传递来排除创建一个数组。但是,静态数组 values 将在被 _values 覆盖之前被初始化,我认为是这样。

因此,我的问题是:这里最好的策略是什么?我该如何编写代码才能触发最少的数组创建?

谢谢!

编辑:

不,我不能使用 std::vector 或 stdlib 中的任何其他数据结构。即使可以,我的问题仍然是关于 原始数组 的最佳策略,而不是如何切换我的方法以支持某些包装器。

【问题讨论】:

  • 除非您的任务是编写自己的数组包装类,否则只需使用 std::vector 并避免所有这些混乱。
  • “结构应该存储一个静态数组”。 要么 struct 应该存储数组,要么 数组应该是静态的,两者一起没有意义。
  • 不要忘记数组,因为参数是mostly lies。
  • @tadman 甚至std::array,看起来更适合这个定义。
  • @tadman std::array 也不必在堆栈上使用,它可以用作更大堆分配的一部分。 std::vector 具有额外间接和堆分配开销的缺点,无论向量对象本身的存储持续时间如何。我不会说偏爱其中一个,我会说使用在特定情况下有意义的那个。

标签: c++ arrays


【解决方案1】:

为了避免复制和指针衰减,_values可以通过引用传递:

template<typename T, std::size_t N>
struct Foo {
    T values[N];
    std::size_t size;
    
    explicit Foo(T const(&_values)[N], std::size_t _size) : size{ _size}{
         std::copy(std::begin(_values), std::end(_values), std::begin(values));
    }
};

https://godbolt.org/z/dnb9zTooG

但最好使用std::array&lt;T, N&gt; values; 成员。


这个答案引起了一些争议。 需要明确的是,如果问题是嵌套数组的初始化,最好不要(任何)构造函数并使用聚合初始化。 如果您想在课堂上做其他事情,这是非常有限的。 最后,无论如何,你正在重新发明std::array。

【讨论】:

  • 这不会在std::copy() 调用之前默认初始化Foo::values 的成员吗?
  • 据我了解,仍然有很多数组创建发生。据我所知,_values 是按值传递的,values 在std::copy() 调用之前仍然被初始化(正如@cdhowie 指出的那样)。另外,我必须再次查看任务规范,但我担心我不会被允许使用std::copy()。
  • @cdhowie,我认为他们一开始就没有初始化。在任何情况下,OP 都应该使用std::array。
  • @RaphaelTarita,如果你不能使用std::array,你最终会间接地重新发明它。也不算太难,最多也就20行吧。
  • 他们问是因为他们不知道这是他们需要的。
【解决方案2】:

你可以做到like this:

#include <cstddef>
#include <type_traits>
#include <utility>
#include <algorithm>

using namespace std;

struct S
{
    S();
    S(int);
    S(S const&);
    S(S&&);
    ~S();
    S& operator=(S const&);
    S& operator=(S&&);
};


template<class F>
struct inplacer
{
    F f_;
    operator std::invoke_result_t<F&>() { return f_(); }
};

template<class F> inplacer(F) -> inplacer<F>;


template<size_t maxN, typename T>
struct myarray
{
    size_t      sz_{};
    T           data_[maxN];
    
    template<class... Ts>
    myarray(Ts&&... args) : sz_{sizeof...(Ts)}, data_{forward<Ts>(args)...} {}

    template<class E, size_t N>
    static myarray make(E const (&a)[N], size_t count = N)
    {
        return make_copy(a, count, make_index_sequence<maxN>());
    }

    template<class E, size_t N>
    static myarray make(E (&&a)[N], size_t count = N)
    {
        return make_move(a, count, make_index_sequence<maxN>());
    }

private:
    template<class E, size_t... Indices>
    static myarray make_copy(E const* p, size_t count, index_sequence<Indices...>)
    {
        auto r = myarray( inplacer{ [&]{ return Indices < count ? p[Indices] : T(); } }... );
        r.sz_ = min(count, maxN);
        return r;
    }

    template<class E, size_t... Indices>
    static myarray make_move(E* p, size_t count, index_sequence<Indices...>)
    {
        auto r = myarray( inplacer{ [&]{ return Indices < count ? move(p[Indices]) : T(); } }... );
        r.sz_ = min(count, maxN);
        return r;
    }
};


auto f()
{
    auto v = myarray<16, int>(1, 2);

    //myarray<100, int> v = {3, 4};

    //static int d[] = {1, 2, 3, 4};
    //auto v = myarray<12, int>::make(d, 3);

    //myarray<4, S> v = {S(1), 2};
    //myarray<4, S> v = {inplacer{ []{ return S(1); } }, 2};

    //static S d[] = {3, 4, 1};
    //auto v = myarray<4, S>::make(d, 2);
    //auto v = myarray<4, S>::make(move(d));

    return v;
}

我敢打赌,这可以在很多方面进行改进,我将把它留给你...

注意事项:

  • 无论我多么努力——我都无法让 GCC default-initialize 数组中未使用的元素。我试图用trick&lt;T&gt;() 调用的结果填充它们,但它不起作用......它抱怨未使用的变量并坚持用零填充数组的其余部分。

    template<class T> T trick() { T r; return r; } 
    
  • 这意味着这个类的效率很低,更好的方法是使用原始存储并手动控制它(也许尝试将数组放入无限制的联合中?但要注意——编译器have bugs在处理联合时)

  • 还要记住,编译器不喜欢太多的函数参数——相关的优化只适用于相对较小的maxN 值。为myarray&lt;1000, int&gt;::make(d, 3) 生成的代码看起来很可怕,编译时间也很短......

  • myarray ctor 允许类似 aggregate-init 的初始化,它非常接近但不完全相同。它不允许就地构造(仅仅是因为在调用之前需要构造参数)——您可以使用inplacer 来克服这个问题(参见f() 中的示例之一)

【讨论】:

    猜你喜欢
    • 2011-02-08
    • 1970-01-01
    • 2023-04-05
    • 2021-01-31
    • 1970-01-01
    • 2014-10-07
    • 2011-06-04
    • 1970-01-01
    • 2013-09-22
    相关资源
    最近更新 更多