2013-07-26 9 views
5

C++ 11標準では、標準に準拠するアロケータが連続したメモリブロックへのポインタを返す必要があるかどうかを確認できません。標準的なアロケータは連続したメモリを割り当てる必要がありますか?

std::vector(23.3.6.1/1)の連続ストレージ要件はそうであるように思われます(それ以外の場合は、std::vectorを任意の標準準拠アロケータで使用することは不可能です)。しかし、どんな説明も大歓迎です。

同等の質問は:私はいつも(おそらくは例えばhereについて記載したよう++ポインタ普通生Cにallocate()によって返さジェネリックpointerタイプを変換した後)、ポインタ算術演算を介しallocate()によって返されたメモリブロックを移動することができますか?

+2

Erm、それ以外の方法でストレージを利用できるようになりますか? –

+3

アロケータは他のどのようなポインタを返すことができますか?どのように非連続メモリブロックへのポインタを返すのですか? – Xeo

+0

@ Xeo:用意された '' pointer''型でナビゲートすることができる断片化された記憶域を返すかもしれませんか? – bluescarni

答えて

5

はい、allocator::pointerのポインタ演算が期待どおりに機能するという意味で、連続している必要があります。

考えてみると、返されるメモリは物理的に連続していることはめったにありません。最新のCPUには仮想メモリがあり、はこの仮想メモリ内で解釈されるため、連続して見えるだけです。

+0

証拠お願いします! – BoBTFish

+2

@BoBTFish:C++標準ではすべてを正式に指定するわけではありませんが、 'allocator :: construct(pointer、val)'は 'allocator :: allocate'によって返されたポインタを処理する必要があります。 'allocate'は複数の要素のメモリを(非常に意図的に)返すことができるので、' T * pointer'の場合は 'pointer + 1、pointer + 2 'などに追加要素があります。 'allocator :: pointer!= T *'の例外を作ると、 'pointer + N'は常にN + 1番目の要素を指していなければなりません。それは連続したメモリの定義です。 – MSalters

+0

問題の核心は、「ランダムアクセス・イテレータを使用して到達可能」という意味の「連続性」は、必ずしもメモリ・アドレス・レベルでの連続性を意味するものではないということです。 – bluescarni

1

contiguousの意味によって異なります。あなたのプログラムに見られるようなメモリは、必ず連続しているか、オフセット/配列を配列に計算するために "うまく動作しません"。 10の整数値を割り当てる場合は、ptr[0]を最初のものにし、最後にptr[9]とします。ptrは1つのポインタに過ぎないため、1つの連続したメモリブロックを指すことができます。

実際の物理メモリでは、OSが判断して決定するもので、連続していてもいなくてもかまいません。アプリケーションメモリを「どこからでも」得ることができます。アロケータAを考えると

+0

連続していると、生のポインタ演算で到達可能な連続したメモリアドレスを占めることになります。 – bluescarni

+0

答えははいです。 –

2

は、私がA::allocate(n)によって返されたすべてのpのためならばk間隔[0,n)std::addressof(*(p + n - 1)) + 1 == std::addressof(*p) + nにあるときAstd::addressof(*p) + k == std::addressof(*(p + k))連続しメモリを提供することを言うでしょう。

アロケータ要件(§17.6.3.5[allocator.requirements])ではこのプロパティが必要ではありませんが、vector(特にvector::data())を実装する方法は想像できません。 (a)アロケータ要件に何か不足している、(b)アロケータ要件が不十分である、または(c)vectorがアロケータに一般要件を超えて追加要件を課している。ここ

は、連続したメモリ(paste of this code)を提供しないアロケータの「単純な」の例である:nアイテムのためのスペースを割り当てるように求められたら

#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; 
} 

ScaledAllocator2 * nためのスペースを割り当てます。そのポインタ型は、ポインタ演算にも必要なスケーリングを実行します。実際には、2n項目の配列を割り当て、偶数番目のスロットのみをデータに使用します。

ScaledAllocatorが満たしていないアロケータ要件を誰でも見ることができますか?

編集:この質問への答えは、批判的にアロケータ要件テーブルのメンバ関数allocate(n)の効果の標準の説明の意味にかかっ:「メモリがnタイプTのオブジェクトが、オブジェクトが構築されていないために割り当てられています。 "私はすべてが、p == allocate(n)を指定し、p + kk[0,n]に、p + kをに、逆参照できることを意味するということにすべて同意すると思います。言い換えれば、アロケータのポインタ型の領域内で連続しているメモリブロック。

std::vector::data()の説明で非常に間接的に暗示されていますが、明示されていませんが、メモリもローポインタの領域で連続している必要があります(正式な命題は最初の段落で詳しく説明しています)。 (a)すべてのアロケータに適用される連続性要件を明示するか、(b)そのコンセプトをContiguousAllocatorコンセプトに追加し、std::vectorContiguousAllocatorが必要であると指定するとよいでしょう。

+0

この例を考えていただきありがとうございます。これはまさに私が意味していることです。 – bluescarni