Может ли вектор Boost Container управлять памятью через неочищенные указатели?

У меня есть структура, похожая на указатель, которая идет вместо указателя.
Разница с указателем заключается в том, что он имеет дополнительную информацию, которую (также специальный) распределитель может использовать для освобождения памяти.

Эта структура, подобная указателю, хорошо работает для всех основных целей.
Я могу выделить и освободить память, разыменование, приращение,->, так далее.

Теперь я хочу использовать эти указатели для управления STL-подобным контейнером.
Ранее я понял, что вектор STL в принципе не может обрабатывать неочищенные указатели.
T* слишком жестко запрограммирован, и стандарт в основном исключает все, что не является указателем.

Вдохновленный Boost.Interprocess ‘ offset_ptr<T> Я решил использовать Boost.Container vector, который очень настраиваемый и в принципе может управлять всем, распределитель передается boost::container::vector может обрабатывать все, что похоже на указатель.

Сейчас класс boost::container::vector<T, myallocator_with_special_pointer<T>> могу сделать что угодно … кроме resize()!!

Глядя на код в boost/container/vector.hpp кажется, что процесс изменения размера (который в основном состоит из выделения, за которым следует копирование (или перемещение) и освобождение), включает в себя необработанные указатели.

Оскорбительная строка:

  [line 2729:] T * const new_buf = container_detail::to_raw_pointer
(allocator_traits_type::allocate(this->m_holder.alloc(), new_cap, this->m_holder.m_start));

Который позже сопровождается

  [line 3022:] this->m_holder.start(new_start);  // new_start is the same as new_buf above.
// member ::start(pointer&) will need to convert a raw pointer to the pointer typedef.

Обе строки абсолютно убивают возможность использования всего, что не является raw_pointer, Даже если у меня есть оператор преобразования в необработанный указатель, другая информация о специальном указателе будет потеряна.

Кажется довольно глупым, что эта маленькая деталь запрещает использование не сырых указателей. Учитывая все усилия, чтобы контейнер был общим (например, определение pointer typedef), почему эта часть кода использует T* только для изменения размера?

Другими словами, почему Boost Container не использует эту строку вместо

  [alternative] pointer const new_buf =
allocator_traits_type::allocate(this->m_holder.alloc(), new_cap, this->m_holder.m_start);

Есть ли обходной путь или альтернативный способ использовать вектор Boost Container для обработки неочищенных указателей?

Boost.Container говорит на своей странице руководства http://www.boost.org/doc/libs/1_64_0/doc/html/container/history_and_reasons.html#container.history_and_reasons.Why_boost_container

Boost.Container — это продукт долгой разработки, которая началась
в 2004 году с экспериментальной библиотекой Shmem, которая впервые использовала
стандартных контейнеров в разделяемой памяти. Шмем включен модифицированный SGI
Код контейнера STL настроен для поддержки не-сырец allocator::pointer типы
и распределители состояний. После рассмотрения Shmem был принят как
Boost.Interprocess и эта библиотека продолжали дорабатывать и улучшать
эти контейнеры.

Текущая реализация (в контексте изменения размера) идет вразрез с этой целью проекта.


Я задал здесь менее конкретный вопрос о других особенностях распределителей: Можно ли настроить вектор STL? "ссылка" тип?


Для справки распределитель, который указывает специальный указатель (который распространяется на контейнер), выглядит примерно так:

template<class T>
struct allocator{
using value_type = T;
using pointer = array_ptr<T>; // simulates T*
using const_pointer = array_ptr<T const>; // simulates T const*
using void_pointer = array_ptr<void>; // simulates void*
using const_void_pointer = array_ptr<void const>; // simulates void const*
some_managed_shared_memory& msm_;
allocator(some_managed_shared_memory& msm) : msm_(msm){}
array_ptr<T> allocate(mpi3::size_t n){
auto ret = msm_.allocate(n*sizeof(T));
return static_cast<array_ptr<T>>(ret);
}
void deallocate(array_ptr<T> ptr, mpi3::size_t = 0){
msm_.deallocate(ptr);
}
};

Полный рабочий код http://coliru.stacked-crooked.com/a/f43b6096f9464cbf

#include<iostream>
#include <boost/container/vector.hpp>

template<typename T>
struct array_ptr;

template<>
struct array_ptr<void> {
using T = void;
T* p;
int i; //some additional information

//    T& operator*() const { return *p; }
T* operator->() const { return p; }

//    operator T*() const { return p; }
template<class TT>
operator array_ptr<TT>() const{return array_ptr<TT>((TT*)p, i);}
operator bool() const{return p;}
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr){}
array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
template<class Other>
array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};

template<>
struct array_ptr<void const> {
using T = void const;
T* p;
int i; //some additional information

//    T& operator*() const { return *p; }
T* operator->() const { return p; }

operator T*() const { return p; }
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr){}
array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
template<class Other>
array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};

template<typename T>
struct array_ptr {
T* p;
int i; //some additional information

T& operator*() const { return *p; }
T* operator->() const { return p; }
T& operator[](std::size_t n) const{
assert(i == 99);
return *(p + n);
}
bool operator==(array_ptr const& other) const{return p == other.p and i == other.i;}
bool operator!=(array_ptr const& other) const{return not((*this)==other);}

//    operator T*() const { return p; }
array_ptr& operator++(){++p; return *this;}
array_ptr& operator+=(std::ptrdiff_t n){p+=n; return *this;}
array_ptr& operator-=(std::ptrdiff_t n){p-=n; return *this;}
array_ptr operator+(std::size_t n) const{array_ptr ret(*this); ret+=n; return ret;}
std::ptrdiff_t operator-(array_ptr const& other) const{return p - other.p;}
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr), i(0){}

operator bool() const{return p;}

array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
array_ptr(T* ptr) : p(ptr), i(0){}
array_ptr(int) : p(nullptr), i(0){}
array_ptr(array_ptr<void> const& other) : p(static_cast<T*>(other.p)), i(other.i){}
};

struct some_managed_shared_memory {
array_ptr<void> allocate(size_t n) { return array_ptr<void>(::malloc(n), 99); }
void  deallocate(array_ptr<void> ptr) { if (ptr) ::free(ptr.p); }
};

template<typename T>
struct allocator{
using value_type = T;
using pointer = array_ptr<T>; // simulates T*
using const_pointer = array_ptr<T const>; // simulates T const*
using void_pointer = array_ptr<void>; // simulates void*
using const_void_pointer = array_ptr<void const>; // simulates void const*

some_managed_shared_memory& msm_;
allocator(some_managed_shared_memory& msm) : msm_(msm){}
array_ptr<T> allocate(size_t n){
auto ret = msm_.allocate(n*sizeof(T));
return static_cast<array_ptr<T>>(ret);
}
void deallocate(array_ptr<T> ptr, std::size_t = 0){
msm_.deallocate(ptr);
}
};

int main() {
some_managed_shared_memory realm;
boost::container::vector<int, allocator<int> > v(10, realm);
assert( v[4] == 0 );
v[4] = 1;
assert( v[4] == 1 );
for(std::size_t i = 0; i != v.size(); ++i) std::cout << v[i] << std::endl;
for(auto it = v.begin(); it != v.end(); ++it) std::cout << *it << std::endl;

// none of these compile:
v.push_back(8);
assert(v.size() == 11);
v.resize(100);
std::cout << v[89] << std::endl; // will fail an assert because the allocator information is lost
//v.assign({1,2,3,4,5});
}

5

Решение

Я смотрел на вещи.

TL; DR выглядит так: поддерживаются не необработанные указатели, но в некоторых операциях им требуется неявное преобразование из raw. Независимо от того, задумано это или нет, я не знаю, но, похоже, это не противоречит цели проекта.

На самом деле это очень похоже на историю поддержки распределителей: контейнеры STL имели поддержку пользовательских распределителей, но не Stateful распределители (имеется в виду, не подлежащие построению по умолчанию типы распределителей).

Версии распределителя

Сначала я попробовал некоторые из версий распределителя:

using version = boost::container::version_0; // seems unsupported, really
using version = boost::container::version_1;
using version = boost::container::version_2; // does different operations

Но это не имело (решающего) эффекта. Может быть, в документации есть подсказки.

Указатель Арифметика

После этого я посмотрел на конкретные ошибки. Глядя на приведенную строку / ошибку, меня осенило, что необработанный указатель мог быть случайностью. Глядя на вывод этих:

std::cout << boost::container::container_detail::impl::version<allocator<int> >::value << "\n";

array_ptr<int> p;
auto rawp = boost::container::container_detail::to_raw_pointer(p);
std::cout << typeid(rawp).name() << "\n";

std::cout << typeid(p).name() << "\n";
std::cout << typeid(p + 5).name() << "\n";
std::cout << typeid(p - 5).name() << "\n";

Показывает что-то вроде

1
int*
array_ptr<int>
int*
int*

¹ преттифицировано с помощью c++filt -t

Это привело меня к определению арифметики указателей:

template <typename T, typename N>
array_ptr<T> operator+(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p+n, p.i); }

template <typename T>
array_ptr<T>& operator++(array_ptr<T>& p) { return ++p.p, p; }

template <typename T>
array_ptr<T> operator++(array_ptr<T>& p, int) { auto q = p.p++; return array_ptr<T>(q, p.i); }

template <typename T, typename N>
array_ptr<T> operator-(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p-n, p.i); }

template <typename T>
ptrdiff_t operator-(array_ptr<T> const& a, array_ptr<T> const& b) { return a.p - b.p; }

Теперь вывод становится

1
int*
array_ptr<int>
array_ptr<int>
array_ptr<int>

Многие другие варианты использования успешно компилируются с этими определениями. Предполагая, что данные «аннотации» внутри array_pointer действителен после приращения, он не должен терять информацию о распределителе

Настоящий виновник

С этим из пути, некоторые вещи все еще не компилируются. В частности, в некоторых местах распределитель pointer Тип создается из необработанного указателя. Это терпит неудачу, потому что нет подходящего конструктора преобразования «по умолчанию». Если вы объявляете конструкторы со значением данных необязательным, все компилируется, но вы можете утверждать, что это приводит к потере информации, поскольку существует путь от

 array_pointer<T> p;
auto* rawp = to_raw_pointer(p);
array_pointer<T> clone(rawp); // oops lost the extra info in p

НАБЛЮДЕНИЯ

Обратите внимание, что, как вы, по-видимому, поняли (судя по закомментированным операторам), добавление аргумента конструктора по умолчанию устраняет необходимость в арифметических операциях (кроме предварительного увеличения).

Однако их добавление гарантирует, что путь преобразования с потерями выбирается реже, что может быть важно для вашего варианта использования.

ДЕМО ВРЕМЯ

Жить на Колиру

#if COMPILATION_INSTRUCTIONS
clang++ -std=c++14 -Wall -Wfatal-errors $0 -o $0x.x && $0x.x $@ && rm -f $0x.x; exit
#endif

#define DEFAULT_DATA = 0
#define DEFINE_ARITHMETIC_OPERATIONS

#include <iostream>
#include <boost/container/vector.hpp>
#include <typeinfo>

template<typename T>
struct array_ptr {
T* p;
int i; //some additional information

T& operator*() const { return *p; }
T* operator->() const { return p; }

operator T*() const { return p; }

array_ptr(){}
//array_ptr(std::nullptr_t) : p(nullptr), i(0){}
array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}

};

template<>
struct array_ptr<void> {
using T = void;
T* p;
int i; //some additional information

//    T& operator*() const { return *p; }
T* operator->() const { return p; }

operator T*() const { return p; }
template<class T>
operator array_ptr<T>() const{return array_ptr<T>((T*)p, i);}
//    array_ptr& operator++(){++p; return *this;}
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr){}
array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}
template<class Other>
array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};

template<>
struct array_ptr<void const> {
using T = void const;
T* p;
int i; //some additional information

//    T& operator*() const { return *p; }
T* operator->() const { return p; }

operator T*() const { return p; }
//    array_ptr& operator++(){++p; return *this;}
//  template<class Other> array_ptr(array_ptr<Other> const& other) : p(other.p), i(other.i){}
array_ptr(){}
array_ptr(std::nullptr_t) : p(nullptr){}
array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}
template<class Other>
array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};

struct some_managed_shared_memory {
array_ptr<void> allocate(size_t n) { return array_ptr<void>(::malloc(n), 99); }
void  deallocate(array_ptr<void> ptr) { if (ptr) ::free(ptr.p); }
};

template<typename T>
struct allocator{
using version = boost::container::version_1;

using value_type = T;
using pointer = array_ptr<T>; // simulates T*
using const_pointer = array_ptr<T const>; // simulates T const*
using void_pointer = array_ptr<void>; // simulates void*
using const_void_pointer = array_ptr<void const>; // simulates void const*

some_managed_shared_memory& msm_;
allocator(some_managed_shared_memory& msm) : msm_(msm){}
array_ptr<T> allocate(size_t n){
auto ret = msm_.allocate(n*sizeof(T));
return static_cast<array_ptr<T>>(ret);
}
void deallocate(array_ptr<T> ptr, std::size_t = 0){
msm_.deallocate(ptr);
}
};

#ifdef DEFINE_ARITHMETIC_OPERATIONS
template <typename T, typename N>
array_ptr<T> operator+(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p+n, p.i); }

template <typename T>
array_ptr<T>& operator++(array_ptr<T>& p) { return ++p.p, p; }

template <typename T>
array_ptr<T> operator++(array_ptr<T>& p, int) { auto q = p.p++; return array_ptr<T>(q, p.i); }

template <typename T, typename N>
array_ptr<T> operator-(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p-n, p.i); }

template <typename T>
ptrdiff_t operator-(array_ptr<T> const& a, array_ptr<T> const& b) { return a.p - b.p; }
#endifint main() {
std::cout << boost::container::container_detail::impl::version<allocator<int> >::value << "\n";

if (1) { // some diagnostics
array_ptr<int> p;
auto rawp = boost::container::container_detail::to_raw_pointer(p);
std::cout << typeid(rawp).name() << "\n";

std::cout << typeid(p).name() << "\n";
std::cout << typeid(p + 5).name() << "\n";
std::cout << typeid(p - 5).name() << "\n";
}

some_managed_shared_memory realm;
boost::container::vector<int, allocator<int> > v(10, realm);
assert( v[4] == 0 );
v[4] = 1;
assert( v[4] == 1 );
for(std::size_t i = 0; i != v.size(); ++i) std::cout << v[i] << std::endl;

// these compile:
v.push_back(12);
v.resize(100);
v.assign({1,2,3,4,5});
}

Печать

1
Pi
9array_ptrIiE
9array_ptrIiE
9array_ptrIiE
0
0
0
0
1
0
0
0
0
0
2

Другие решения

Других решений пока нет …