【问题标题】:construct a vector in range without copying在范围内构造一个向量而不复制
【发布时间】:2016-04-25 11:15:38
【问题描述】:

我有一个类,它包装了一个大字节数组,这些字节是网络数据包。该类实现了一个队列并提供(除其他外)front() 函数,该函数返回构成队列中最旧数据包的 const 字节向量。

class Buffer{
  unsigned char data[65536];
  unsigned int offset;
  unsigned int length;
  [...]//other fields for maintaining write ptr etc.

public:
  const std::vector<unsigned char> front(){
    return std::vector<unsigned char>(data + offset, data + offset + length);
  }

  //other methods for accessing the queue like
  //pop(), push(), clean() and so forth...
  [...]
}

front() 函数的上述实现的性能受到从当前数据包占用的范围内不必要的复制字节的影响。由于向量是常量,因此不需要复制数据。我想要的是在已经存储在缓冲区中的数据上创建一个向量。当然向量的析构函数不应该释放内存。

【问题讨论】:

  • 你打算如何使用front()函数?也许声明两个返回 const 迭代器的函数会更好:std::vector&lt;unsigned char&gt;::const_iterator front_begin() constfront_end()
  • 您真的需要返回std::vector 吗?你不能把一个视图返回到缓冲区吗?
  • 事实上,目前我有两个函数:readPtr() 返回数据 + 偏移量和 front() 只返回长度。我只是想通过返回一个向量来简化它,所以通过调用 front() 用户将得到一切。
  • 你也许可以使用 std::vector 来做到这一点,并带有一些可怕的分配器魔法。向量想要分配内存、释放内存、设置自己的大小以及(在它的一些构造函数中)复制数据。您可以使用自定义分配器停止释放,并且您可以(我认为?)安全地伪造您选择的内存位置的“分配”,也可以使用分配器。您可能可以通过使用不将任何项目放入向量中的构造函数来防止复制(我猜默认的可能有效)。我不知道你会如何处理大小的设置......
  • 哦等等,也许分配器的构造方法可以不初始化向量元素?然后你可以使用一个大小设置的构造函数,或者vector::resize,也许吧?哈哈,在大多数情况下,尝试这些事情是很愚蠢的。您可能不应该为此使用 std::vector。

标签: c++ vector stdvector allocator placement-new


【解决方案1】:

您有一些选择:

  1. 与其返回vector,不如返回const char*
const char* front() {
    return data;
}
  1. 考虑使用标准容器,例如string data 作为您的Buffer 成员。这将允许您执行以下操作:
const string& front() {
    return data;
}
  1. 最好的选择是如果你有 C++17 或访问 experimental::string_view 你可以这样做:
const string_view front() {
    return string_view(data);
}

只是一个约定注释,front 的预期行为将与其他标准容器一样,其中:

返回对容器中第一个元素的引用。
在空容器上调用 front 是未定义的。

[source]

C++ 标准委员会还讨论了在固定大小的数组上将前端应用于裸机:front and back Proposal for iterators Library

因为这种方法更类似于data,它:

返回指向包含容器元素的内存块的指针。

[source]

【讨论】:

  • 我也必须返回长度。这就是我想使用矢量的原因之一。使用字符串会让用户感到困惑,我无法访问 c++17。关于命名——因为我写的类实现了一个队列,所以在我看来使用名称front 是相当合法的。无论如何感谢您的建议。
  • @Patryk 你能帮我理解一下,为你的data 成员使用标准容器会“让用户感到困惑”吗?
  • 对原始数据使用字符串令人困惑,至少对我而言。例如,作为用户,我不会直观地知道 length() 函数会返回什么 - 它是数据长度、直到第一个 \0 之前的字节数还是其他什么。
  • @Patryk 如果string 提供的除vector 之外的任何方法都不能为您带来额外的好处,那么使用vector 非常有意义。但如果是这种情况,那么我的选项 2 建议您使用:“一个标准容器......作为您的Buffer 成员”所以也许vector&lt;char&gt; data?
  • 但我不想返回整个数据,只是以 const 向量的方便形式返回它的一个子集,就像我在创建时不进行复制和分配,在销毁时不进行释放。
【解决方案2】:

如果您希望避免不必要的复制,则需要返回数据视图。您可以提供front_begin()front_end() 函数集:

const char *front_begin() const
{
  return data + offset;
}

const char *front_end() const
{
  return data + offset + length;
}

或者写一个包装类:

class Data
{
private:
  const char *m_Begin;
  const char *m_End;

public:
  Data(const char *begin, const char *end) : m_Begin(begin), m_End(end)
  {
  }

  const char *begin() const
  {
    return m_Begin;
  }

  const char *end() const
  {
    return m_End;
  }
}

并让您的 front() 方法返回其中之一:

Data front()
{
  return Data(data + offset, data + offset + length)
}

如果您使用的是 C++11,那么您可以在基于范围的 for 循环中使用 Data 实例:

Data data = buffer.front();
for(char c : data)
{
  // Do something with the data
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-13
    相关资源
    最近更新 更多