Могу ли я отсоединить std :: vector <char>от данных, которые он содержит? - PullRequest
7 голосов
/ 08 сентября 2010

Я работаю с функцией, которая выдает некоторые данные в виде std::vector<char>, и с другой функцией (представьте устаревшие API), которая обрабатывает данные и принимает const char *, size_t len. Есть ли способ отсоединить данные от вектора, чтобы вектор мог выйти из области видимости перед вызовом функции обработки без копирования данных, содержащихся в векторе (это то, что я подразумеваю под отсоединение ).

Небольшой набросок кода для иллюстрации сценария:

// 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 );
}

Ответы [ 4 ]

15 голосов
/ 08 сентября 2010
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 ); 
} 
4 голосов
/ 08 сентября 2010

Самое простое решение еще не было представлено:

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

  processData( &data[0], data.size() ); 
} 
1 голос
/ 05 июля 2018

Я даю ответ на оригинальный вопрос ниже.Однако имейте в виду, что в большинстве случаев использование vector::data() и реорганизация кода таким образом, чтобы упомянутый вектор не выходил за рамки, использование другого вектора, инициализированного vector(std::move(otherVector)), или замена содержимого другим вектором (vector::swap(otherVector))лучший вариант, как было указано в других ответах.

Тем не менее, существует способ отсоединения данных вектора с помощью специального распределителя.Следующее реализует распределитель Noalloc, который абсолютно ничего не делает.Он не распределяет / конструирует вещи, не освобождает / не разрушает вещи.Также он не содержит нестатических или виртуальных переменных-членов.Поэтому он не должен изменять содержимое экземпляра vector.Внутри detachData мы приводим обычный вектор к вектору, используя вышеупомянутый тип распределителя, переосмысливая его тип и перемещая его содержимое в новый вектор с тем же распределителем.Когда новый вектор выходит из области видимости, он будет пытаться уничтожить и освободить содержимое своего массива.Однако, поскольку мы переопределили эту функциональность в нашем пользовательском распределителе, это абсолютно ничего не делает.Функция возвращает указатель на недавно отсоединенные данные исходного вектора (который теперь содержит nullptr в качестве данных и нулевые элементы).Не забудьте потом уничтожить и освободить элементы массива вручную!

#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;
}
0 голосов
/ 08 сентября 2010

Я бы не советовал, но:

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

Вуаля!

Этот код на самом деле довольно безопасен, поскольку деструктор vec вызовет delete [] 0;, что является безопасной операцией (если вы не получили какую-то странную реализацию stl).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...