我需要一些内存管理,并希望我可以将它建立在某个std
容器上。我的要求是:
所以,我需要通过添加块来扩展,例如std::deque
。但是对于std::deque
,我无法保证通过8个元素进行扩展会给我一个连续的块。并且std::deque
没有capacity
所以我无法“适应”std::deque
。
这意味着我必须自己写,对吗? (注意:我不想知道如何自己编写,但只有当我必须时)。
编辑以澄清:只有在每次扩展时获得的元素块必须是连续的,而不是整个容器 - 这显然与其他要求相矛盾。
编辑jalf 那么这是什么:用于“排序”3D点的空间八度树。树节点指的是立方体单元,并形成与使用指针链接的父母和女儿的链接结构。同级节点未链接,但在内存中相邻。预先不知道节点的总数(因为每个最终节点的点数> 1),但是可以获得估计。在树构建期间,当划分非最终节点时,必须获得最多8个新节点的连续块,然后将其链接到树中。移动或复制这些节点会使任何现有链接(指针)失效。
另一个编辑只是为了澄清一些讨论。任何基于std::vector<T>
的设计都不得使用resize()
和/或reserve()
。在某些条件下,两者都需要复制或移动T
的构造函数。即使从未在这些条件下调用过,代码也无法编译。
答案 0 :(得分:5)
只有一个问题,std::vector
适合您。
它完全是连续的,不仅仅是块状的,可以扩展,保持连续(第2点)。扩展可能意味着重新分配(因此先前获得的指针/迭代器失效,移动),但如果您事先知道总大小(第3点),则可以reserve()
以便不重新分配发生了。
给定向量i
的迭代器v
,您可以通过i - v.begin()
获得正在运行的数字(位置);类似地,给定一个元素的指针p
,p - &v[0]
(第4点)。
捕获是你的观点1.有emplace_back()
,但出于与异常安全相关的原因,std::vector
仍尝试在某处暂时构建元素,然后将移动到他们的永久职位。
假设你有这门课程
struct A
{
A() { }
A(A&&) = delete;
A(const A&) = delete;
};
我可以看到两个解决方法:
派生另一个类B
,默认构造而不是复制/移动构造:
struct B : A
{
B() : A() { }
B(B&&) : A() { }
B(const B&) : A() { }
};
如果你不能这样做,那就制作一个为你做这个的分配器对象:
template<typename T>
struct allocator : std::allocator<T>
{
using std::allocator<T>::allocator;
using std::allocator<T>::construct;
template<typename U>
void construct(U* p, U&&) { construct(p); }
template<typename U>
void construct(U* p, const U&) { construct(p); }
template<typename U>
struct rebind { using other = allocator<U>; };
};
template<>
struct allocator<void> : std::allocator<void> { };
两种情况的使用如下所示(live example):
template<typename C, size_t N = 100>
void test()
{
C c;
c.reserve(N);
for (size_t i = 0; i < N; ++i)
c.emplace_back();
}
int main ()
{
test<std::vector<B> >();
test<std::vector<A, allocator <A> > >();
}
请记住,这样仍有A
的实例被构造然后被丢弃。这是使用std::vector
的一个不幸后果。如果A
足够小并且其默认构造没有任何奇怪的副作用,那么这应该不是问题。
如果你仍然需要扩展超过最初的reserve()
,那么我建议使用这种向量的容器作为块。如果您仍然希望将此元容器视为具有自己的迭代器的单个容器,那么相关的是我自己的join
view及其iterator仅仅是为了一个想法,但这仍然是非常实验性的。我打赌在Boost中还有为此目的的东西,但我并不那么熟悉。
答案 1 :(得分:1)
我建议只使用std::deque<std::vector<T>>
作为自定义类的私有数据成员,以确保:
答案 2 :(得分:1)
这是一个简单的模板化C ++ 11类,它使用指向矢量的指针向量来包含元素,它以块的形式在后面扩展。以前分配的元素的指针在扩展后仍然有效。它支持基于范围的循环和随机访问。我使用std :: vector有两个目的,一个顶层向量保持指向第二级向量,顶层向量用普通push_back扩展,所以指向第二级向量的指针每次到达时都会被复制(非常快)扩展数量为2的幂 - 指定为非常少且不常见。第二级向量分配有在构造Chunky实例时或在扩展期间给出的初始大小,并且大小永远不会改变,它们包含元素(模板的参数),因此指向元素的指针不会被无效。调用extend()。
我为每个100万个元素进行了10个扩展,并且每个元素都可以访问它们,它的运行时间为106毫秒(每个访问平均值为10.6纳秒)。
然后,我将每个10,000个元素的1000个扩展和每个元素的访问权限定时,它运行115毫秒(每个访问平均值为11.5纳秒)。
chunky.h
#ifndef CHUNKY_H
#define CHUNKY_H
#include <vector>
#include <cstddef>
#include <stdexcept>
template<class T> class Chunky {
std::vector< std::vector<T> * > _chunk;
size_t _size = 0;
T* _active = nullptr;
size_t _begin = 0;
size_t _end = 0;
public:
Chunky() = default;
Chunky( size_t guess ) { extend( guess ); }
~Chunky() { for ( auto & it : _chunk ) delete it; }
void extend( size_t x ) {
_chunk.push_back( new std::vector<T>( x ) );
_size += x;
}
size_t size() const { return _size; }
T & operator[]( size_t z ) { return at( z ); }
T & at( size_t z ) {
if ( z < _begin || z >= _end ) {
size_t x = 0;
for( _end = 0; z >= _end && x < _chunk.size(); ++x)
{
_begin = _end;
_end += _chunk[x]->size();
_active = _chunk[x]->data();
}
if (z >= _end) throw std::out_of_range("Chunky at "+std::to_string(z));
}
return _active[z - _begin];
}
class iterator
: public std::iterator<std::forward_iterator_tag, int>
{
Chunky<T> * _tp = nullptr;
size_t _x = 0;
public:
iterator() = default;
iterator(Chunky<T> * tp) : _tp(tp) {}
bool operator!= (const iterator& other) const {
return _tp != other._tp || _x != other._x;
}
T & operator* () const { return _tp->at(_x); }
T * operator->() const { return &_tp->at(_x); }
iterator& operator++ () {
if (++_x >= _tp->size()) {
_x = 0;
_tp = nullptr;
}
return *this;
}
iterator& operator+= ( size_t x ) { while(x--) *this++; return *this; }
friend inline iterator& operator+ ( iterator & lhs, size_t x ) { while(x--) ++lhs; return lhs; }
friend inline iterator operator+ ( iterator lhs, size_t x ) { while(x--) ++lhs; return lhs; }
};
inline iterator begin() { return iterator(this); }
static inline const iterator end() { return iterator(); }
};
#endif
一个简单的测试
#include "chunky.h"
#include <iostream>
#include <chrono>
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::steady_clock;
struct Element
{
int _value = 0;
Element() = default;
Element(const Element&) = delete;
Element(Element&&) = delete;
Element& operator=(Element const&) = delete;
};
int main( int argc, char *argv[] )
{
Chunky<Element> c( 5 ); // 5 default constructed Elements
int i = 0;
// Chunky allows range based for loop
for( auto & it : c ) it._value = 100 + i++;
// Pointers to elements are valid for the lifetime of the container
Element * element_ptr = & c[4]; // Take address of one of the elements
std::cout << "The fifth element has a value of " << element_ptr->_value << std::endl;
size_t previous_size = c.size();
c.extend( 10 ); // 10 more default constructed Elements
std::cout << "Dereferencing pointer to fifth element after extend, value is still " << element_ptr->_value << std::endl;
for( size_t k=0; k < 10; ++k )
c[previous_size + k]._value = 1100 + i++;
// random access to initial elements and newly extended elements
c[3]._value = -3;
c[13]._value = -13;
std::cout << "\nThe Elements contain values of: " << std::endl;
// range based for loop
for( auto & it : c ) std::cout << it._value << std::endl;
steady_clock::time_point start = steady_clock::now();
size_t extend_size = 1e6;
for( size_t x = 0; x < 10; ++x ) {
size_t previous_size = c.size();
c.extend( extend_size ); // 1 million more default constructed Elements
for( size_t k=0; k < extend_size; ++k )
c[previous_size + k]._value = previous_size + k;
}
steady_clock::time_point end = steady_clock::now();
std::cout << "\nExtending 10 times by " << extend_size << " and initializing the Elements took "
<< duration_cast<milliseconds>(end - start).count()
<< " msec.\n";
return 0;
}
运行示例
g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
The fifth element has a value of 104
Dereferencing pointer to fifth element after extend, value is still 104
The Elements contain values of:
100
101
102
-3
104
1105
1106
1107
1108
1109
1110
1111
1112
-13
1114
Extending 10 times by 1000000 and initializing the Elements took 106 msec.
答案 3 :(得分:1)
所有复杂的解决方案有什么用?不会std::vector<std::unique_ptr<T>>
解决问题,或者我错过了什么?
答案 4 :(得分:1)
简短的回答似乎是没有单个标准容器完成这项工作,你必须自己编写。
这被证明比我想象的更困难,因为生成对象的指针必须在内存管理器的生命周期内保持有效(在原始问题中通过请求无法移动或复制的请求实现)。这不包括使用std::vector::resize()
和std::vector::reserve()
。仍然可以设计sequence_container<std::vector<T>>
,但要么为每个新对象块创建另一个vector
,要么预先构造一大块对象,然后将它们放出,直到块耗尽为止。 / p>
为避免这种情况,似乎必须编写一些chunk
类(使用而不是vector
)并处理allocator
问题。这是一个实现:
#include <list>
#include <memory>
template<typename type, typename allocator=std::allocator<type>,
template<typename,typename> class sequence_container = std::list>
class chunk_allocator
{
public:
using object = type;
using pointer = object*;
using size_type = std::size_t;
using alloc_traits = std::allocator_traits<allocator>;
private:
struct chunk
{
allocator alloc;
const pointer beg_data, end_capacity;
pointer end_data;
chunk(size_type cap, const allocator&all)
: alloc(all)
, beg_data(alloc_traits::allocate(alloc,cap))
, end_capacity(beg_data+cap)
, end_data(beg_data) {}
~chunk()
{
if(beg_data==nullptr) return;
for(; --end_data>=beg_data; --end_data)
alloc_traits::destroy(alloc,end_data);
alloc_traits::deallocate(alloc,beg_data,capacity());
}
size_type size() const noexcept { return end_data - beg_data; }
size_type capacity() const noexcept { return end_capacity - beg_data; }
pointer make(size_type n)
{
if(end_data + n > end_capacity)
return nullptr;
auto ptr = end_data;
for(; n; --n,++end_data)
alloc_traits::construct(alloc,end_data);
return ptr;
}
};
using chunk_alloc = typename alloc_traits::template rebind_alloc<chunk>;
using chunk_container = sequence_container<chunk,chunk_alloc>;
using chunk_iterator = typename chunk_container::iterator;
chunk_container chunks;
chunk_iterator last_chunk;
/// no default constructor
chunk_allocator() = delete;
/// no copy
chunk_allocator(chunk_allocator const&) = delete;
chunk_allocator&operator=(chunk_allocator const&) = delete;
public:
/// allow move
chunk_allocator(chunk_allocator&&) = default;
chunk_allocator&operator=(chunk_allocator&&) = default;
/// constructor
explicit
chunk_allocator(size_type initial_capacity, allocator const&alloc=allocator())
: chunks(alloc)
, last_chunk(chunks.emplace(chunks.end(),initial_capacity,alloc)) {}
/// invalid index
static constexpr size_type invalid = ~size_type(0);
/// find index for element, return invalid if not ours
size_type index(const object*ptr) const noexcept
{
size_type n=0;
for(auto c=chunks.begin(); c!=chunks.end(); ++c)
if(c->beg_data <= ptr && ptr < c->end_data)
return n + size_type(ptr-c->beg_data);
else
n += c->size();
return invalid;
}
/// obtain contiguous chunks of objects
/// \param[in] n \# objects in returned chunk
/// \param[in] chunk_size \# objects to allocate should we not have enough
/// \return pointer to first of n contiguous objects
object*create(const size_type n, size_type chunk_size=0)
{
if(n==0)
return nullptr;
if(last_chunk->end_data + n > last_chunk->end_capacity) {
if(chunk_size==0) chunk_size = last_chunk->capacity();
if(chunk_size< n) chunk_size = n;
last_chunk = chunks.emplace(chunks.end(),chunk_size,last_chunk->alloc);
}
return last_chunk->make(n);
}
};
// test
#include <iostream>
struct foo
{
int X;
static int C;
foo() : X(C++) { std::cout<<"foo::foo(): X="<<X<<std::endl; }
foo(foo const&) = delete;
foo&operator=(foo const&) = delete;
foo(foo &&) = delete;
foo&operator=(foo &&) = delete;
};
int foo::C=0;
int main()
{
std::cout<<" chunk_allocator<foo> C(3);"<<std::endl;
chunk_allocator<foo> C(3);
auto a = C.create(1);
std::cout<<" auto a=C.create(1)="<<a<<std::endl;
auto b = C.create(4);
std::cout<<" auto b=C.create(4)="<<b<<std::endl;
auto c = C.create(3);
std::cout<<" auto c=C.create(3)="<<c<<std::endl;
std::cout<<" a="<<a<<" a->X="<<a->X<<" index(a)="<<C.index(a)<<'\n'
<<" b="<<b<<" b->X="<<b->X<<" index(b)="<<C.index(b)<<'\n'
<<" c="<<c<<" c->X="<<c->X<<" index(c)="<<C.index(c)<<'\n';
}