【问题标题】:taking over memory from std::vector从 std::vector 接管内存
【发布时间】:2015-01-25 11:27:48
【问题描述】:

我使用一个对大量数据进行操作的外部库。数据由原始指针传入,加上长度。库不声明指针的所有权,但在处理完数据后调用提供的回调函数(具有相同的两个参数)。

使用std::vector<T> 可以方便地准备数据,我不想放弃这种方便。复制数据是完全不可能的。因此,我需要一种方法来“接管”std::vector<T> 拥有的内存缓冲区,并(稍后)在回调中释放它。

我目前的解决方案如下:

std::vector<T> input = prepare_input();
T * data = input.data();
size_t size = input.size();
// move the vector to "raw" storage, to prevent deallocation
alignas(std::vector<T>) char temp[sizeof(std::vector<T>)];
new (temp) std::vector<T>(std::move(input));
// invoke the library
lib::startProcesing(data, size);

并且,在回调函数中:

void callback(T * data, size_t size) {
    std::allocator<T>().deallocate(data, size);
}

此解决方案有效,因为标准分配器的deallocate 函数忽略了它的第二个参数(元素计数)并简单地调用::operator delete(data)。如果没有,可能会发生不好的事情,因为输入向量的size 可能比它的capacity 小很多。

我的问题是:是否有一种可靠的(按照 C++ 标准)方式来接管 std::vector 的缓冲区并在以后“手动”释放它?

【问题讨论】:

  • 你需要接管整个向量。
  • 如果vector 有一个detach 函数会很好......但它没有
  • @T.C.:但我没有地方可以存储它——输入产生和释放发生在程序的两个不同部分
  • 我不明白对齐存储的需求。为什么不只是unique_ptr&lt;vector&lt;T&gt;&gt; temp(new vector&lt;T&gt;(move(input)));?此外,您的解决方案仅在 T 是一种可简单破坏的类型时才有效,否则您需要在每个元素上调用 allocator&lt;T&gt;::destroy。要回答您的问题,没有简单的方法从 vector 接管内存,您也许可以使用自定义分配器来解决问题,但我会坚持使用当前的解决方案。
  • 叹息 - 库使用错误回调签名的另一个案例。如果回调签名是 void (*callback)(T * data, size_t size, void * user_data)startProcessing(T* data, size_t size, void * userdata),那么您将有一个简单的解决方案。

标签: c++ c++11 stdvector allocator


【解决方案1】:

您无法从向量中获取内存的所有权,但您可以通过另一种方式解决您的潜在问题。

这是我的处理方法 - 由于静态全局变量而不是线程安全的,它有点 hacky,但可以通过对 registry 对象的访问进行一些简单的锁定来实现。

static std::map<T*, std::vector<T>*> registry;
void my_startProcessing(std::vector<T> * data) {
  registry.put(data->data(), data);
  lib::startProcesing(data->data(), data->size());
}

void my_callback(T * data, size_t length) {
  std::vector<T> * original = registry.get(data);
  delete original;
  registry.remove(data);
}

现在你可以这样做了

std::vector<T> * input = ...
my_startProcessing(input);

但要小心!如果在调用my_startProcessing 后向输入添加/删除元素,则会发生不好的事情 - 库的缓冲区可能会失效。 (您可能被允许更改向量中的值,因为我相信这将正确写入数据,但这也取决于库允许的内容。)

如果T=bool 也不起作用,因为std::vector&lt;bool&gt;::data() 不起作用。

【讨论】:

  • 看起来不错。如果我找不到避免全局变量的方法,我会在上面撒上一点std::mutexstd::unique_ptr,应该没问题。谢谢!
【解决方案2】:

您可以在向量上创建自定义类构建。

这里的重点是在SomeData 构造函数中使用移动语义。

  • 您无需复制即可获得准备好的数据(请注意,源向量将被清除)
  • 数据将被thisData向量析构函数正确处理
  • 源向量可以毫无问题地处理

由于底层数据类型将是数组,因此您可以计算起始指针和数据大小(参见下面的SomeDataImpl.h):

SomeData.h

#pragma once
#include <vector>

template<typename T>
class SomeData
{
    std::vector<T> thisData;

public:
    SomeData(std::vector<T> && other);

    const T* Start() const;
    size_t Size() const;
};

#include "SomeDataImpl.h"

SomeDataImpl.h

#pragma once

template<typename T>
SomeData<T>::SomeData(std::vector<T> && otherData) : thisData(std::move(otherData)) { }

template<typename T>
const T* SomeData<T>::Start() const {
    return thisData.data();
}

template<typename T>
size_t SomeData<T>::Size() const {
    return sizeof(T) * thisData.size();
}

用法示例:

#include <iostream>
#include "SomeData.h"

template<typename T>
void Print(const T * start, size_t size) {
    size_t toPrint = size / sizeof(T);
    size_t printed = 0;

    while(printed < toPrint) {
        std::cout << *(start + printed) << ", " << start + printed << std::endl;
        ++printed;
    }
}

int main () {
    std::vector<int> ints;
    ints.push_back(1);
    ints.push_back(2);
    ints.push_back(3);

    SomeData<int> someData(std::move(ints));
    Print<int>(someData.Start(), someData.Size());

  return 0;
}

【讨论】:

    【解决方案3】:

    您不能以任何可移植的方式执行此操作,但您可以以可能适用于大多数 C++ 实现的方式执行此操作。在 VS 2017 上进行快速测试后,此代码似乎可以工作。

    #include <iostream>
    
    #include <vector>
    
    using namespace std;
    
    template <typename T>
    T* HACK_stealVectorMemory(vector<T>&& toStealFrom)
    {
        // Get a pointer to the vector's memory allocation
        T* vectorMemory = &toStealFrom[0];
    
        // Construct an empty vector in some stack memory using placement new
        unsigned char buffer[sizeof(vector<T>)];
        vector<T>* fakeVector = new (&buffer) vector<T>();
    
        // Move the memory pointer from toCopy into our fakeVector, which will never be destroyed.
        (*fakeVector) = std::move(toStealFrom);
    
        return vectorMemory;
    }
    
    int main()
    {
        vector<int> someInts = { 1, 2, 3, 4 };
        cout << someInts.size() << endl;
    
        int* intsPtr = HACK_stealVectorMemory(std::move(someInts));
    
        cout << someInts.size() << endl;
    
        cout << intsPtr[0] << ", " << intsPtr[3] << endl;
    
        delete intsPtr;
    }
    

    输出:

    4
    0
    1, 4
    

    【讨论】:

      猜你喜欢
      • 2018-12-13
      • 2010-10-14
      • 2018-12-02
      • 2015-04-08
      • 2010-10-14
      • 1970-01-01
      • 2011-01-19
      • 1970-01-01
      • 2018-10-23
      相关资源
      最近更新 更多