我在 C++11 标准中找不到任何迹象表明是否需要符合标准的分配器来返回指向连续内存块的指针。
(23.3.6.1/1)上的连续存储要求std::vector
似乎暗示了这一点(否则似乎不可能std::vector
与任意符合标准的分配器一起使用)。但任何澄清都将受到欢迎。
一个等效的问题是:我是否可以始终allocate()
通过指针算术返回的内存块(可能在将pointer
返回的泛型类型转换allocate()
为纯原始 C++ 指针之后,例如此处所述)?
我在 C++11 标准中找不到任何迹象表明是否需要符合标准的分配器来返回指向连续内存块的指针。
(23.3.6.1/1)上的连续存储要求std::vector
似乎暗示了这一点(否则似乎不可能std::vector
与任意符合标准的分配器一起使用)。但任何澄清都将受到欢迎。
一个等效的问题是:我是否可以始终allocate()
通过指针算术返回的内存块(可能在将pointer
返回的泛型类型转换allocate()
为纯原始 C++ 指针之后,例如此处所述)?
是的,它必须是连续的,因为指针算术allocator::pointer
按预期工作。
如果您考虑一下,返回的内存很少在物理上是连续的。它只是看起来是连续的,因为现代 CPU 有虚拟内存,并且X*
在这个虚拟内存中被解释。
给定一个分配器A
,我会说它A
提供了连续的内存,如果对于任何p
返回的A::allocate(n)
,std::addressof(*p) + k == std::addressof(*(p + k))
whenk
是在区间[0,n)
和std::addressof(*(p + n - 1)) + 1 == std::addressof(*p) + n
。
我没有看到分配器要求中需要此属性(§17.6.3.5 [allocator.requirements]),但我无法想象没有它如何实现vector
(尤其是vector::data()
)。要么(a)我在分配器要求中遗漏了某些东西,(b)分配器要求未指定,要么(c)vector
对其分配器施加了超出一般要求的附加要求。
这是不提供连续内存的分配器的“简单”示例(此代码的粘贴):
#include <cstddef>
#include <iostream>
#include <iterator>
#include <limits>
#include <memory>
template <typename T>
class ScaledPointer : public std::iterator<std::random_access_iterator_tag, T> {
T* ptr;
public:
ScaledPointer() = default;
ScaledPointer(T* ptr) : ptr(ptr) {}
template <typename U>
explicit ScaledPointer(U* ptr) : ptr(static_cast<T*>(ptr)) {}
template <typename U>
explicit ScaledPointer(const ScaledPointer<U>& other) :
ptr(static_cast<T*>(other.ptr)) {}
explicit operator bool () const { return bool{ptr}; }
T& operator * () const {
return *ptr;
}
T* operator -> () const {
return ptr;
}
T& operator [] (std::ptrdiff_t n) const {
return ptr[2 * n];
}
ScaledPointer& operator ++ () {
ptr += 2;
return *this;
}
ScaledPointer operator ++ (int) {
ScaledPointer tmp(*this);
++*this;
return tmp;
}
ScaledPointer& operator -- () {
ptr -= 2;
return *this;
}
ScaledPointer operator -- (int) {
ScaledPointer tmp(*this);
--*this;
return tmp;
}
template <typename U, typename V>
friend bool operator == (const ScaledPointer<U>& u, const ScaledPointer<V>& v) {
return u.ptr == v.ptr;
}
template <typename U, typename V>
friend bool operator != (const ScaledPointer<U>& u, const ScaledPointer<V>& v) {
return !(u == v);
}
template <typename U, typename V>
friend bool operator < (const ScaledPointer<U>& u, const ScaledPointer<V>& v) {
return u.ptr < v.ptr;
}
template <typename U, typename V>
friend bool operator > (const ScaledPointer<U>& u, const ScaledPointer<V>& v) {
return v < u;
}
template <typename U, typename V>
friend bool operator <= (const ScaledPointer<U>& u, const ScaledPointer<V>& v) {
return !(v < u);
}
template <typename U, typename V>
friend bool operator >= (const ScaledPointer<U>& u, const ScaledPointer<V>& v) {
return !(u < v);
}
ScaledPointer& operator += (std::ptrdiff_t n) {
ptr += 2 * n;
return *this;
}
friend ScaledPointer operator + (const ScaledPointer& u, std::ptrdiff_t n) {
ScaledPointer tmp = u;
tmp += n;
return tmp;
}
ScaledPointer& operator -= (std::ptrdiff_t n) {
ptr -= 2 * n;
return *this;
}
friend ScaledPointer operator - (const ScaledPointer& u, std::ptrdiff_t n) {
ScaledPointer tmp = u;
tmp -= n;
return tmp;
}
friend std::ptrdiff_t operator - (const ScaledPointer& a, const ScaledPointer& b) {
return (a.ptr - b.ptr) / 2;
}
};
template <typename T>
class ScaledAllocator {
public:
typedef ScaledPointer<T> pointer;
typedef T value_type;
typedef std::size_t size_type;
pointer allocate(size_type n) {
const std::size_t size = (n * (2 * sizeof(T)));
void* p = ::operator new(size);
std::cout << __FUNCTION__ << '(' << n << ") = " << p << std::endl;
std::fill_n((unsigned*)p, size / sizeof(unsigned), 0xFEEDFACEU);
return pointer{p};
}
void deallocate(pointer p, size_type n) {
std::cout << __FUNCTION__ << '(' << &*p << ", " << n << ')' << std::endl;
::operator delete(&*p);
}
static size_type max_size() {
return std::numeric_limits<size_type>::max() / 2;
}
template <typename U, typename V>
friend bool operator == (const ScaledAllocator<U>&, const ScaledAllocator<V>&) {
return true;
}
template <typename U, typename V>
friend bool operator != (const ScaledAllocator<U>&, const ScaledAllocator<U>&) {
return false;
}
};
#include <algorithm>
#include <vector>
int main() {
using namespace std;
cout << hex << showbase;
vector<unsigned, ScaledAllocator<unsigned>> vec = {0,1,2,3,4};
for_each(begin(vec), end(vec), [](unsigned i){ cout << i << ' '; });
cout << endl;
auto p = vec.data();
for(auto i = decltype(vec.size()){0}, n = vec.size(); i < n; ++i)
cout << p[i] << ' ';
cout << endl;
}
当被要求为n
项目ScaledAllocator
分配空间时,为2 * n
. 它的指针类型也为其指针算术执行必要的缩放。实际上,它分配了一个包含 2n 个项目的数组,并且只使用偶数编号的插槽来存储数据。
任何人都可以看到ScaledAllocator
无法满足的分配器要求吗?
编辑:这个问题的答案关键取决于标准对分配器要求表中成员函数影响的描述的含义:“内存是为类型的对象allocate(n)
分配的,但没有构造对象。” 我想我们都会同意,这意味着给定then是 all in的有效指针,并且可以取消引用in 。换句话说,在分配器指针类型的域中是连续的内存块。n
T
p == allocate(n)
p + k
k
[0,n]
p + k
k
[0,n)
不清楚的是——尽管它的描述非常间接地暗示了std::vector::data()
——内存也需要在原始指针的域中是连续的(我的第一段中详述的正式命题)。如果该标准(a)明确适用于所有分配器的连续性要求,或者(b)将该要求添加到一个ContiguousAllocator
概念并指定std::vector
需要一个ContiguousAllocator
.
这取决于你的意思contiguous
。正如您的程序所看到的那样,内存肯定是连续的,或者它不会“正常工作”来计算数组的偏移量/索引等。如果您分配 10 个整数值,您希望ptr[0]
成为第一个,并ptr[9]
成为最后一个 - 因为ptr
它只是一个指针,它只能指向一个单一的、连续的内存块。
在引擎盖下,在真实的物理内存中,它可能是连续的或不连续的 - 这是操作系统确定和决定的东西,它很可能从“任何地方”提供应用程序内存。