【问题标题】:"Moving" sequential containers to pointers将顺序容器“移动”到指针
【发布时间】:2019-02-19 16:21:20
【问题描述】:

我正在为网络连接构建一个缓冲区,您可以在其中显式分配内存,或者您可以通过一些顺序容器(例如:std::vector,std::array)自行提供这些内存块存储在列出我们稍后用于读/写操作的内容。 (处理多个读/写请求需要块) 我对最后一部分有疑问,我想创建一个指向容器数据的指针,然后告诉容器不再关心它的数据。 所以像移动语义这样的东西。

std::vector<int> v = {9,8,7,6,5,4,3,2,1,0};
std::vector<int> _v(std::move(v));

其中_v 的所有值vv 都处于安全状态。

问题是如果我只是在容器的生命周期结束后为v.data() 创建一个指针,指针指向的数据会随容器一起释放。 例如:

// I would use span to make sure It's a sequential container 
// but for simplicity i use a raw pointer
// gsl::span<int> s;
int *p;
{
   std::vector<int> v = {9,8,7,6,5,4,3,2,1,0};
   // s = gsl::make_span(v);
   p = v.data();
}

for(int i = 0; i < 10; ++i) 
    std::cout << p[i] << " ";

std::cout << std::endl;

现在p 包含一些内存垃圾,我需要该向量以前拥有的内存。

我也尝试过v.data() = nullptr,但v.data() 是右值,因此无法分配它。您有什么建议吗,或者这可能吗?

编辑: 为了更清楚我想要实现的目标:

class readbuf_type
{
    struct item_type // representation of a chunk
    {
        uint8_t * const data;
        size_t size;

        inline item_type(size_t psize)
            : size(psize)
            , data(new uint8_t[psize])
        {}

        template <std::ptrdiff_t tExtent = gsl::dynamic_extent>
        inline item_type(gsl::span<uint8_t,tExtent> s)
            : size(s.size())
            , data(s.data())
        {}

        inline ~item_type()
        { delete[] data; }
    };

    std::list<item_type> queue; // contains the memory
public:

    inline size_t read(uint8_t *buffer, size_t size); // read from queue

    inline size_t write(const uint8_t *buffer, size_t size); // write to queue

    inline void *get_chunk(size_t size)
    {   
        queue.emplace_back(size);
        return queue.back().data;
    }

    template <std::ptrdiff_t tExtent = gsl::dynamic_extent>
    inline void put_chunk(gsl::span<uint8_t,tExtent> arr)
    { 
        queue.emplace_back(arr);
    }
} readbuf;

我有 get_chunkfunction 基本上只是分配大小的内存,我有 put_chunk 我正在努力解决的问题,我需要这个的原因是因为在你可以写入这个队列之前你需要分配内存然后将您尝试写入的缓冲区(向量,数组)中的所有元素复制到队列中。 比如:

std::vector<int> v = {9,8,7,6,5,4,3,2,1,0};
// instead of this
readbuf.get_chunk(v.size);
readbuf.write(v.data(), v.size());

// we want this
readbuf.put_chunk({v});

由于我们正在为分布式系统开发内存,因此内存至关重要,这就是为什么我们要避免不必要的分配、复制。

ps.这是我的第一篇文章,如果我一开始不准确,请见谅..

【问题讨论】:

  • 使用标准容器是不可能的,但你为什么要这样做呢?
  • 我不确定您要解决什么问题(我看不出代码与您的问题的其余部分有何关系)。但我认为这可能会对您有所帮助,除非分配器不兼容,否则如果您将 v 移动到 v_,指向 v.data(); 的指针将指向 v_.data();
  • @marko1777 据我所知,不可能将向量的数据“释放”到这样的原始指针。在v 的生命周期结束后使用p 是未定义的行为,尽管我不怀疑这个UB 表现出自己产生了您期望的结果(似乎可以工作)。这不会持久,对代码或其运行的上下文的任何更改都可能改变行为。
  • 这是正确的。这对于标准容器本身是不可行的。您将必须实现自己的自定义容器。自定义容器只不过是一个带有unique_ptrstruct 到带有真实数据的std::vector 的单个struct,而“不再关心”只不过是将unique_ptr 移动到其他地方。
  • int *p; 更改为 std::vector&lt;int&gt; 而不是 int*。然后使用p = std::move(v); 而不是p = v.data();

标签: c++ c++17 cpp-core-guidelines


【解决方案1】:

不,不可能以您建议的方式“窃取”标准向量的缓冲区 - 或任何其他标准容器。

您已经展示了一种解决方案:将缓冲区移动到另一个向量中,而不是仅仅获取缓冲区的地址(或另一个非拥有引用)。从向量中移动会转移内部缓冲区的所有权。

实现这样的自定义向量类是可能的,它的缓冲区可能会被盗,但是向量不能使它成为可能是有原因的。如果你不顾一切地释放资源,证明你的程序的正确性是相当困难的。您是否考虑过如何防止数据泄露?上面的解决方案更简单,更容易验证正确性。

另一种方法是重新构建程序,使对容器数据的引用不会超过容器本身(或任何无效操作)。

【讨论】:

  • 感谢您的回答!我更新了问题。可悲的是,没有选择以这种方式组织代码..
  • 如果你真的想做这样的事情而不是实现一个自定义容器,你可以只实现一个与标准相同的自定义分配器,但在解除分配时它不会释放内存.我不明白为什么泄漏会成为问题,因为当您销毁包含它的对象时,您会在析构函数中释放内存。
【解决方案2】:

不幸的是,向量的内存区域无法与 std::vector 对象分离。即使您向 std::vector 对象插入一些数据,也可以删除内存区域。因此,以后使用这个内存区域是不安全的,除非你确定这个特定的 std::vector 对象存在并且没有被修改。

解决这个问题的方法是分配一个新的内存区域,并将向量的内容复制到这个新分配的内存区域。可以安全地访问新分配的内存区域,而不必担心 std::vector 对象的状态。

std::vector<int> v = {1, 2, 3, 4};
int* p = new int[v.size()];
memcpy(p, v.data(), sizeof(int) * v.size());

在你使用完这个内存区域后不要忘记删除这个内存区域。

delete [] p;

【讨论】:

  • 感谢您的回复,这将是一个好主意,但我们应该尽量减少分配。我编辑了我的问题,所以也许更清楚我为什么需要这个。
【解决方案3】:

您的错误在于认为指针“包含”内存。它不包含任何东西,垃圾或整数或其他内容。它是一个指针。它指向东西。你已经删除了那些东西,没有把它转移到其他地方,所以它不能再工作了。

一般来说,您将需要一个容器来放置这些信息,可以是另一个向量,甚至是您自己手工制作的数组。仅仅有一个指向数据的指针并不意味着你有数据。

此外,由于不可能要求向量将其缓冲区放弃给非向量事物,因此在这种特殊情况下,向量实际上是您唯一的机会。目前尚不清楚为什么这对您来说不是一个足够好的解决方案。 :)

【讨论】:

    【解决方案4】:

    不确定你想达到什么目的,但我会使用这样的移动语义:

    #include <iostream>
    #include <memory>
    #include <vector>
    
    int main() {
    std::unique_ptr<std::vector<int>> p; 
    {
       std::vector<int> v = {9,8,7,6,5,4,3,2,1,0};
       p = std::move(make_unique<std::vector<int>>(v));
    }
    
    for(int i = 0; i < 10; ++i) 
       std::cout << (*p)[i] << " ";
    
    std::cout << std::endl;
    
    
    return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      相关资源
      最近更新 更多