代码之家  ›  专栏  ›  技术社区  ›  PaperBirdMaster

为什么静态大小的数组类型不能是容器类型?

  •  3
  • PaperBirdMaster  · 技术社区  · 7 年前

    我有一个静态大小数组的别名,使用它简单明了:

    using triplet_t = std::uint8_t[3];
    
    //           vvvvvvvvvvvvvvvvvv <--- easier than std::uint8_t(&triplet)[3]
    void f(const triplet_t &triplet) { /* whatever */ }
    
    triplet_t t{}; // As good as std::uint8_t t[3]{};
    
    t[0] = '0';
    t[1] = '1';
    t[2] = '2';
    for (auto &v : t) std::cout << v << ' ';
    std::cout << '\n';
    
    // So far so good...
    triplet_t t3[3]{};
    for (auto &r : t3)
        for(auto &v : r)
            v = 42;
    

    我甚至可以在容器中使用别名:

    std::vector<triplet_t> vt;
    

    或者我曾经这么想,因为一旦你使用 vt 它失败了:

    vt.push_back({});
    

    通用条款8.0.0 201711

    error: parenthesized initializer in array new [-fpermissive]
    { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    error: request for member '~unsigned char [3]' in '* __p', which is of non-class type 'unsigned char [3]'
    destroy(_Up* __p) { __p->~_Up(); }
                        ~~~~~~^~~
    

    问题似乎是,在展开所有模板欺骗之后,新的放置被称为转发括号中提供的所有参数,显然这不是初始化静态大小数组的方法。

    而且,不知怎的,容器想到了 triplet_t

    std::vector<std::uint8_t[3]> vt;
    vt.push_back({});          // Boom!
    vt.push_back({255, 0, 0}); // Ouch!
    

    但使用 struct 使用相同的内存布局:

    struct rgb { std::uint8_t r, g, b; };
    std::vector<rgb> vt;
    vt.push_back({});          // Nice!
    vt.push_back({255, 0, 0}); // Cool!
    

    2 回复  |  直到 7 年前
        1
  •  5
  •   YSC    7 年前

    std::vector documentaion ,你可以找到 T 必须满足的要求 CopyAssignable 可复制。

    这意味着(简化):使用 v t 类型的两个实例 ,表达式 t = v 必须合法。显然,如果 T 是本机数组,但情况并非如此(不能将C数组分配给另一个),并且 std::vector<T> 将是不正确的。

    解决方案是定义 triplet_t 作为:

    using triplet_t = std::array<std::uint8_t, 3>;
    
    void f(const triplet_t &triplet) { /* whatever */ }
    
    triplet_t t{};
    
    t[0] = '0';
    t[1] = '1';
    t[2] = '2';
    for (auto &v : t) std::cout << v << ' ';
    std::cout << '\n';
    
    // So far so good...
    triplet_t t3[3]{};
    for (auto &r : t3)
        for(auto &v : r)
            v = 42;
    
    std::vector<triplet_t> vt;
    
    vt.push_back({});
    
        2
  •  4
  •   Jodocus    7 年前

    根据 documentation , push_back 要求值类型为 CopyInsertable MoveInsertable . 让我们看看 definition :

    T型为 可插入副本 放入容器中 十、 其value_类型与相同 T 如果 T 可插入X,并且给定[…]以下表达式格式正确:

    std::allocator_traits<A>::construct(m, p, v);
    

    因此,在C数组的情况下,对于标准分配器,有一个表达式 like

    ::new((void *)p) int[3](std::forward<int[3]>(v))
    

    其中v为数组类型。据英国《金融时报》报道,这是一种格式错误的做法 specification of new :

    • 如果类型是数组类型,则初始化对象数组。
      • 如果缺少初始值设定项,则每个元素都默认初始化
      • 如果初始值设定项是一对空括号,则每个元素都是值初始化的。
      • 如果初始值设定项是用大括号括起来的参数列表,则数组是聚合初始化的。

    不存在允许非空parantesse的数组类型语法。

    这个论证与 移动可插入 类别 总之,拟议的解决方案是(已经提到)使用 std::array 它本身不是数组类型,因此可以通过标准分配器的语法正确初始化 construct 作用

    最后一点注意:严格的别名规则只允许使用 unsigned char , signed char char . 虽然几乎可以肯定 std::uint8_t 只是实现中的一个别名typedef,标准中对此没有保证。

    推荐文章