Can I detach a std::vector from the data it

2019-04-27 13:25发布

问题:

I'm working with a function which yields some data as a std::vector<char> and another function (think of legacy APIs) which processes data and takes a const char *, size_t len. Is there any way to detach the data from the vector so that the vector can go out of scope before calling the processing function without copying the data contained in the vector (that's what I mean to imply with detaching).

Some code sketch to illustrate the scenario:

// Generates data
std::vector<char> generateSomeData();

// Legacy API function which consumes data
void processData( const char *buf, size_t len );

void f() {
  char *buf = 0;
  size_t len = 0;
  {
      std::vector<char> data = generateSomeData();
      buf = &data[0];
      len = data.size();
  }

  // How can I ensure that 'buf' points to valid data at this point, so that the following
  // line is okay, without copying the data?
  processData( buf, len );
}

回答1:

void f() { 
  char *buf = 0; 
  size_t len = 0; 
  std::vector<char> mybuffer; // exists if and only if there are buf and len exist
  { 
      std::vector<char> data = generateSomeData(); 
      mybuffer.swap(data);  // swap without copy
      buf = &mybuffer[0]; 
      len = mybuffer.size(); 
  } 

  // How can I ensure that 'buf' points to valid data at this point, so that the following 
  // line is okay, without copying the data? 
  processData( buf, len ); 
} 


回答2:

The simplest solution has not even been presented yet:

void f() { 
   std::vector<char> data = generateSomeData(); 

  processData( &data[0], data.size() ); 
} 


回答3:

I'm giving an answer to the original question below. However keep in mind that in most cases using vector::data() and reorganizing the code so that the mentioned vector doesn't go out of scope, using a different vector initialized with vector(std::move(otherVector)) or swapping contents with another vector (vector::swap(otherVector)) is the better option as was pointed out in other answers.

Nevertheless there is a way to actually detach the data of a vector using a custom allocator. The following implements the allocator Noalloc that does absolutely nothing. It neither allocates/constructs stuff nor deallocates/destructs things. Also it doesn't contain any non-static or virtual member variables. So it shouldn't change the contents of an instance of vector. Inside detachData we cast an ordinary vector to a vector using the aforementioned allocator type by reinterpreting its type and move its content to a new vector with the same allocator. As the new vector runs out of scope it will try to destruct and deallocate its array contents. However as we've overridden that functionality within our custom allocator this does absolutely nothing. The function returns a pointer to the newly detached data of the original vector (which now holds a nullptr as data and zero elements). Don't forget to destroy and deallocate the elements in the array manually afterwards!

#include <iostream>
#include <vector>
#include <new>

// Our custom allocator
template <class T>
struct Noalloc {
    typedef T value_type;
    Noalloc() = default;
    template <class U> constexpr Noalloc(const Noalloc<U>&) noexcept {}
    T* allocate(std::size_t n)
    {
        return nullptr;
    }
    void deallocate(T* p, std::size_t) noexcept {}
    template<class... Args> void construct(T* p, Args&&... args) {}
    void destroy(T* p) {}
};
template <class T, class U>
bool operator==(const Noalloc<T>&, const Noalloc<U>&) { return true; }
template <class T, class U>
bool operator!=(const Noalloc<T>&, const Noalloc<U>&) { return false; }

// detach the data from a vector and return its pointer
template<typename T>
    T* detachData(std::vector<T>& vec)
{
    T* dataPtr = vec.data();
    // swap contents with a new vector
    // that uses "Noalloc" as allocator but is otherwise identical
    std::vector<T, Noalloc<T>> detachHelper
        (std::move(*reinterpret_cast<std::vector<T, Noalloc<T>>*>(&vec)));
    return dataPtr;
    // "detachHelper" runs out of scope here
    // But it will neither destruct nor deallocate
    // given data due to our custom allocator
}

// destroy and deallocate the data manually
template<typename T>
    void destroyData(T* data, size_t n, size_t capacity)
{
    // should be pretty self explanatory...
    std::allocator<T> alloc;
    for(size_t i = 0; i < n; i++)
    {
        std::allocator_traits<std::allocator<T>>::destroy(alloc, data+i);
    }
    std::allocator_traits<std::allocator<T>>::deallocate(alloc, data, capacity);
}

int main()
{
    int* testData = nullptr;
    size_t size = 0;
    // For vectors data size and actually allocated size
    // may differ. So we need to keep track of both.
    // If you want to avoid this you may call "vector::shrink_to_fit()"
    // This should resize the vector to fit its data.
    // However the implementation is not required to fulfill this request.
    // So beware!
    size_t capacity = 0;
    {
        std::vector<int> test{1,2,3,4};
        // copy size and data pointer
        size = test.size();
        capacity = test.capacity();
        testData = detachData(test);
        // "test" runs out of scope here.
        // However after calling "detachData(test)"
        // it doesn't contain any data
    }
    // Print the contents of the array
    for(size_t i = 0; i < size; i++)
    {
        std::cout << testData[i] << std::endl;
    }
    // Don't forget to destroy the data yourself!
    // The original vector doesn't exist anymore
    destroyData(testData, size, capacity);
    std::cout << "data destroyed successfully" << std::endl;
}


回答4:

I wouldn't recommend it, but:

char* ptr = NULL;
int len = -1;
{
    vector<char> vec;
    /* ... fill vec with data ... */
    vec.push_back(NULL); // dont forget to null terminate =)
    ptr = &vec.front();
    len = vec.size();
    // here goes...
    memset((void*)&vec, 0, sizeof(vec));
}
// vec is out of scoop, but you can still access it's old content via ptr.
char firstVal = ptr[0];
char lastVal = ptr[len-1];
delete [] ptr; // don't forget to free

Voila!

This code is actually pretty safe as vec's destructor will call delete [] 0;, which is a safe operation (unless you got some strange implementation of stl).



标签: c++ stl vector