【问题标题】:C-style array vs std::array for library interface用于库接口的 C 样式数组与 std::array
【发布时间】:2018-02-16 15:14:25
【问题描述】:

我想编写一个带有提供读取功能的接口的库。 C 风格的数组容易出错,但允许传递任意大小的缓冲区。 C++ 数组更安全,但必须使用大小构造。

// interface.h

// C-style array
int read (std::uint8_t* buf, size_t len);

// C++ array
int read (std::array<std::uint8_t, 16>& buff)

我怎样才能两全其美?

我在考虑函数模板,但它似乎不适用于库接口。

template <size_t N>
int read (std::array<std::uint8_t, N>& buf);

编辑 std::vector 可能是一个不错的候选,但如果我们认为 char*std::array 没有动态分配。

编辑我非常喜欢gsl::span 的解决方案。我坚持使用 C++14,所以没有std::span。我不知道使用第三个库 (gsl) 是否会成为问题/允许。

编辑我不认为在另一种类型上使用char 会对答案产生一些影响,所以更清楚的是操纵字节。我把char改成std::uint8_t

编辑由于 C++11 保证返回 std::vector 将被移动而不是复制,因此返回 std::vector&lt;std::uint8_t&gt; 是可以接受的。

std::vector<std::uint8_t> read();

【问题讨论】:

  • 使用std::vector?
  • 如果N 是一个编译时间常数,并且您不关心发布read 的实现,并且您不需要您的API 与C 兼容,那么就没有任何意义模板版本有问题。
  • gsl::span 怎么样?
  • 很难说你在做什么,但你可能会考虑基于迭代器的接口,不需要特定的容器。
  • std::vector 具有动态分配,但 char*std::array 没有。

标签: c++ arrays


【解决方案1】:

您可以做标准库所做的事情:使用一对iterators

template <typename Iter> int read(Iter begin, Iter end)
{
    // Some static assets to make sure `Iter` is actually a proper iterator type
}

它为您提供了两全其美的优势:稍微更好的安全性和读取缓冲区任意部分的能力。它还允许您读取非连续容器。

【讨论】:

  • “安全”级别与动态数组相同。
  • 还可以考虑使用迭代器特征来限制迭代器的类型,具体取决于它提供的功能。
  • 带有一组迭代器的读取函数是非常不寻常的。想解释一下标准 void* 和尺寸的好处吗?
  • @SergeyA 这并没有那么更安全。但至少它可以防止像void *buf = blah; int status = read(&amp;buf, buf_len); 这样的愚蠢错误。
  • @smac89 static_assert 可以更好地描述问题。我宁愿得到 "static_assert: Iter is not a valid %iterator_category% iterator" 而不是大量神秘的错误消息。
【解决方案2】:

如何才能两全其美?

通过使用std::vector:

  • std::arrays:它比 C 数组更安全。
  • 类似于 C 数组:它允许您使用必须能够采用任意大小数组的函数。

编辑std::vector 不一定暗示动态分配(如动态存储持续时间)。这取决于使用的 分配器。您仍然可以提供用户指定的stack allocator

【讨论】:

    【解决方案3】:

    我会违背原则说,对于read-type 函数,采用void* 指针和大小可能是最好的选择。这是世界上任何未格式化的读取函数所采用的方法。

    【讨论】:

    • 我想强制执行类型安全
    • char* 如何实现类型安全?
    • @SergeyA:因为 OP 正在处理文本。如果是字节,它将是std::byte,或unsigned char。无论如何,这就是代码与人类交流的内容。
    • @Cheersandhth.-Alf 上次我检查时,std::*stream 未格式化的读取函数正在使用char*(准确地说,char_type 这是默认流中的char*)。
    • 流主要是关于文本的,并且它们被记录在案,并且它们的历史可以追溯到旧的 C。std::byte 进入了 C++17。
    【解决方案4】:

    您为什么不使用gsl::span,它的目的是消除一系列连续对象的指针和长度参数对?这样的事情会起作用:

    int read(gsl::span<uint8_t> buf)
    {
        for (auto& elem : buf)
        {
            // Do whatever with elem
        }
    }
    

    唯一的问题是,不幸的是,gsl::span 不是 C++ 标准的一部分(也许它可能在 C++20 中),安装它需要像 GSL-lite 这样的库

    Here 是有关 span 的更多详细信息,来自 Herb Sutter。

    【讨论】:

      【解决方案5】:

      你真的关心底层容器的类型吗?

      template<typename Iterator>
      int read_n(Iterator begin, size_t len);
      

      假设这个函数返回读取的元素数量,我也会将返回类型更改为size_t

      char *dyn = new char[20];
      char stat[20];
      std::vector<char> vec(20);
      read(dyn, 20);
      read(stat 20);
      read(vec.begin(), 20);
      

      【讨论】:

      • 在 c++ 中采用迭代器和大小是非常不寻常的。无论如何,实现几乎肯定需要计算结束迭代器。如果需要支持非随机访问迭代器,问题会变得更糟。
      • 确实,但实际上我在考虑 ContiguousIterator,它允许实现使用memcpy(&amp;(*begin), ..., len);
      • 如果你想依赖 memcpy 那么为什么不简单地使用指针呢?这是人们在 C++ 中所期望的。如果您只是想为 ContiguousIterator 提供一个允许更好性能的专业化,我仍然会使用 begin 和 end 迭代器而不是长度,那么也可以传递类似 dyn.begin()+5, dyn.end() 的东西,而不必从长度中减去开始偏移量
      【解决方案6】:

      我认为在设计 lib 的界面时,您需要考虑将在何处使用它。

      带有“char *”的 C 接口库可用于多种语言(C、C++ 等)。使用 std::array 会限制您的库的潜在客户。

      第三种可能的变体:

      struct buf_f allocBuf();
      
      int rmBuf( struct buf_t b );
      
      int read( struct buf_f b );
      
      char * bufData( struct buf_f b );
      
      size_t bufSize( struct buf_f b );
      

      当然可以用 C++ 以更优雅的方式重写它。

      【讨论】:

        【解决方案7】:

        您可以使用制作一个包装函数,它是一个委托给 C 接口函数的模板函数:

        int read(std::uint8_t* buf, size_t len);
        
        template <size_t N>
        int read(std::array<std::uint8_t, N>& buf)
        {
          return read(buf.data(), buf.size());
        }
        

        当我需要通过 C ABI 进行某些操作但又不想失去 C++ 提供的一些便利时,我发现这样的构造很有用,因为模板函数被编译为库客户端代码的一部分,并且不会'不需要与 C ABI 兼容,而模板函数调用与 C ABI 兼容

        【讨论】:

          【解决方案8】:

          只需返回一个std::vector&lt;uint8_t&gt;,除非这是一个 DLL,在这种情况下使用 C 风格的接口。

          注意:问题从char更改为uint8_t后,答案从std::string更改为std::vector

          【讨论】:

          • 问题中没有任何内容表明缓冲区包含字符串。它可能是二进制数据,虽然std::string 可能能够存储它们,但由其他容器更好地表示。
          • @FrançoisAndrieux:std::string 非常适合任何字节序列。谷歌如此使用它。 “由其他容器更好地代表”是一种观点,如果没有支持证据,我不会给予太多信任,因为您没有注意到这里的普通 char 类型。
          • 匿名投票者,考虑理性讨论而不是盲目投票(这只是说你无法争论你的立场)。考虑到std::string 是唯一合理的前期设计。是什么让您对当前公认的最佳做法投反对票?
          • 我不是匿名的。我不认为将std::string 用作“唯一的前期合理设计”,也不认为它是“当前公认的最佳实践”std::string 通常应该包含可显示的字符数据。虽然这不是正式要求,但对于任何不熟悉它的人来说,这都是一个令人讨厌的惊喜。也许它在您的环境中运行良好,但我不同意它应该被推荐为这个问题的一般解决方案,因此不赞成。我确实了解 SSO 可能会有什么用处,但我考虑使用 std::vector&lt;char&gt; 来代替使意图更加清晰。
          • @FrançoisAndrieux:首先std::string在几个二进制接口中以这种方式使用,例如Google protocol buffers(我刚刚用谷歌搜索)。其次,OP 模糊地指出了文本(通过现代 C++ 中的char),并且没有提到任何关于二进制的内容。哦,有a tool usage argument 支持std::string。和同上转换参数。为什么不实用?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-05-20
          • 1970-01-01
          • 1970-01-01
          • 2011-05-24
          • 2015-07-27
          • 1970-01-01
          • 2017-01-15
          相关资源
          最近更新 更多