Отказать std::vector от удаления его данных
У меня есть следующий случай:
T* get_somthing(){
std::vector<T> vec; //T is trivally-copyable
//fill vec
T* temp = new T[vec.size()];
memcpy(temp, vec.data(), vec.size() * sizeof(T));
return temp;
}
Я хочу избавиться от процесса копирования, вернув std::vector::data
следующим образом:
T* get_somthing(){
std::vector<T> vec; //T is trivally-copyable
//fill vec
return temp.data();
}
Однако это неверно, поскольку данные будут удалены при вызове vec
деструктора.
Итак, как я могу запретить vec удалять свои данные? Другими словами, я хочу какой-то move-idiiom от std::vector
до С++ Raw Dynamic Array.
P.S. Изменение дизайна не является вариантом. Использование std::vector
обязательно. Возврат pointer
в array
также является обязательным. Becauese Это оболочка между двумя модулями. Один вектор потребности другой указатель потребности.
Ответы
Ответ 1
P.S. Изменение дизайна не является вариантом. Использование std::vector обязательно. Возврат указателя к массиву также является обязательным.
Изменение дизайна - ваш лучший вариант. Я рекомендую пересмотреть эту позицию.
Существует (в настоящее время †) нет способа "украсть" буфер вектора, поэтому учитывая (глупые ††) ограничения, указанные в вопросе, копирование это путь.
† Tomasz Lewowski связал предложение, которое изменило бы это, если оно будет включено в будущий стандарт: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4359.pdf (Edit: как указано, оно было отклонено С++ 17)
†† Глупо, до тех пор, пока это не будет оправдано конкретными требованиями.
Это оболочка между двумя модулями. Один вектор потребности другой указатель потребности.
Предположительно, другой интерфейс, которому нужен указатель, делегирует уничтожение буфера вызывающему абоненту, возможно, используя какой-то вызов, например void delete_somthing(T*)
. На мой взгляд, взять собственность, не отдавая ее, было бы очень плохой дизайн.
Если у вас есть контроль над уничтожением, вы можете сохранить вектор на карте и стереть вектор, когда указатель передается для уничтожения:
std::unordered_map<T*, std::vector<T>> storage;
T* get_somthing(){
std::vector<T> vec; //T is trivally-copyable
//fill vec
T* ptr = vec.data();
storage[ptr] = std::move(vec);
return ptr;
}
void delete_somthing(T* ptr){
storage.erase(ptr);
}
Ответ 2
В С++ 11 нет возможности выпустить буфер из вектора.
Такое расширение стандарта было предложено С++ 17: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4359.pdf, но, как T.C. указал, что он был отклонен: https://issues.isocpp.org/show_bug.cgi?id=81
В этом нет никакой удачи.
Также был отправлен связанный вопрос, на который уже был дан ответ, и объясняет ту же проблему: Уничтожить std::vector без освобождения памяти
Если вы можете общаться с любой из этих библиотек, вы можете попробовать пользовательские распределители или другие странные вещи (например, привязать себя к внутренней реализации библиотеки и возиться с частными векторными данными), но на самом деле этого не делать.
Ответ 3
Ниже приведен пример кода, как это сделать с помощью пользовательского распределителя. Это предполагает, что вы можете сделать vector
фактически использование настраиваемого распределителя. Кроме того, распределитель использует статическую переменную для управления уничтожением внутреннего буфера. Я проверил под VS2015, и его реализация вызывает освобождение в ~ векторе только для внутреннего буфера - то есть он не управляет никакими другими распределениями, используя этот распределитель.
Это взлом - и я не уверен, какие последствия может иметь его использование. Небезопасный поток (но может быть легко исправлен после того, как разрешить thread_dealloc-поток локально):
http://coliru.stacked-crooked.com/a/5d969a6934d88064
#include <limits>
#include <vector>
#include <iostream>
template <class T>
class my_alloc {
std::allocator<T> alloc;
public:
static bool allow_dealloc;
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
pointer allocate(size_type num, const void* = 0) { return alloc.allocate(num); }
void deallocate(pointer p, size_type num) {
if (allow_dealloc)
alloc.deallocate(p, num*sizeof(T)); }
// Squashed as less important
template <class U> struct rebind { typedef my_alloc<U> other; };
pointer address(reference value) const { return &value; }
const_pointer address(const_reference value) const { return &value; }
my_alloc() throw() { }
my_alloc(const my_alloc&) throw() { }
template <class U> my_alloc(const my_alloc<U>&) throw() { }
~my_alloc() throw() { }
size_type max_size() const throw() { return (std::numeric_limits<size_t>::max)() / sizeof(T); }
void construct(pointer p, const T& value) { alloc.construct(p, value); }
void destroy(pointer p) { p->~T(); }
};
template <typename T>
bool my_alloc<T>::allow_dealloc = true;
int main()
{
int* data = 0;
size_t size = 0;
{
my_alloc<int>::allow_dealloc = true;
std::vector<int, my_alloc<int>> vec= { 0, 1, 2, 3 };
vec.push_back(4);
vec.push_back(5);
vec.push_back(6);
my_alloc<int>::allow_dealloc = false;
data = vec.data();
size = vec.size();
}
for (size_t n = 0; n < size; ++n)
std::cout << data[n] << "\n";
my_alloc<int> alloc;
alloc.deallocate(data, size);
}
Ответ 4
Я не знаю, понравится ли вам это очень хакерское решение, я определенно не буду использовать его в производственном коде, но, пожалуйста, подумайте:
#include <iostream>
using namespace std;
#include <vector>
template<class T>
T* get_somthing(){
std::vector<T> vec = {1,2,3}; //T is trivally-copyable
static std::vector<T> static_vector = std::move(vec);
return static_vector.data();
}
int main() {
int * is = get_somthing<int>();
std::cout << is[0] << " " << is[1] << " " << is[2];
return 0;
}
поэтому, как вы можете видеть внутри get_somthing
, я определяю статический вектор того же типа, который вам нужен, и используйте std::move
на нем и возвращаем его data()
. Он достигает того, чего вы хотите, но это опасный код, поэтому, пожалуйста, используйте добрую старую копию данных снова, и подождите, пока N4359 попадет в компиляторы основного потока.
Живая демонстрация по адресу: http://ideone.com/3XaSME
Ответ 5
Если есть возможность использовать интеллектуальные указатели, я бы рекомендовал std::shared_ptr
с псевдонимом:
template<typename T>
std::shared_ptr<T> get_somthing(){
using Vector = std::vector<T>;
using ReturnT = std::shared_ptr<T>;
std::vector<T>* vec = new std::vector<T>;
//fill vec
std::shared_ptr<Vector> vectorPtr(vec); // (1)
std::shared_ptr<T> aliasedPtr(vectorPtr, vec->data()); // (2)
return aliasedPtr;
}
(1) создаст общий указатель на вектор, который будет псевдонимом
(2) создает общий указатель, который уничтожит aliased shared_ptr вместо удаления содержащихся данных
Ответ 6
Изменить: эта идея не работает, потому что нет возможности предотвратить неявный вызов деструкторам базовых классов (спасибо, molbdnilo). То, что их называют, это, если я думаю об этом, хорошая вещь.
Я не был полностью уверен, что это жизнеспособно (и любопытно, что говорят другие), но можно ли унаследовать от вектора и переопределить его деструктор (ничего не делать)? Даже если ~vector()
не является виртуальным (существует ли требование в стандарте для того, чтобы быть или не быть виртуальным?), Это должно работать до тех пор, пока вы явно используете свой тип.
Наследуя, вы сохраните все преимущества, в частности управление памятью, за исключением финального бита (который вам не нужен).
Ответ 7
Разве это не путь, чтобы просто распределить вектор динамически? То, что традиционно управляются ресурсы со временем жизни, не связанным с областью, и я не вижу причин изобретать что-то необыкновенное.
Конечно, этот вектор должен быть уничтожен через некоторое время; что может потребовать сохранить его адрес где-то в качестве побочного эффекта get_somthing()
, но даже тогда эта стратегия кажется более чистой, чем любая другая идея.
Ответ 8
Первое, что вам нужно сделать, это встать, пойти к ответственному за этот дизайн и (устно профессионально) ударить его/ее по лицу: это mess.
Затем есть способ в С++ 11 иметь std::vector
с автоматической продолжительностью хранения и не вызывать его деструктор:
Поместите std::vector
в union
Так же:
template<typename T>
union Ugly {
std::vector<T> vec;
Ugly() {
new (&vec) std::vector<T>(); // Construct
}
~Ugly() {
// Don't destruct
}
};
T* get_something(){
Ugly mess;
//fill mess.vec
return mess.vec.data();
}
Я не уверен на 100%, что это все еще считается допустимым С++ 11, но он должен "работать". Теперь извините меня, мне нужно вымыть руки, чтобы избавиться от плачущего чувства стыда за этот код...
О, и еще одно: как вы собираетесь освободить память, которую выделил std::vector
? Вы знаете, вы не можете (надежно) использовать указатель, возвращенный функцией-членом data()
для этого!
Ответ 9
Хорошо, не пробуйте это дома, это не приятно. Это скорее эксперимент, чем реальный ответ.
(Ну, требования/дизайн тоже не приятны: "Играйте в глупые игры, выигрывайте глупые призы" )
в вашем cpp:
#define private public // good luck for the code review
#define protected public
#include <vector> // (must be the first occurence in the TU)
#undef private // do not abuse good things...
#undef protected
template<typename T>
T* my_release(std::vector<T>& v){
std::vector<T> x; // x: the local vector with which we mess around
std::swap(x, v); // the given vector is in an OK, empty state now.
T* out = x._M_impl._M_start; // first, get the pointer you want
// x will be destructed at the next '}'.
// The dtr only use _M_start and _M_finish, make sure it won't do anything.
x._M_impl._M_start = nullptr;
x._M_impl._M_finish = nullptr;
// no need to say, the internal state of 'x' is bad, like really bad...
// also we loose the capacity information, the actual allocator...
// -> good luck with memory leaks...
return out;
}
// usage example
int main(){
std::vector<int> vi{1,2,3,4,5,6,7,8,9};
auto n = vi.size();
int* pi = release(vi);
for(size_t i=0; i<n; ++i)
std::cout << pi[i] << ", ";
return 0;
}
печатает 1, 2, 3, 4, 5, 6, 7, 8, 9,