9

コンテナを作成していて、ユーザーがカスタムアロケータを使用できるようにしたいのですが、参照または値のどちらでアロケータを渡す必要があるのか​​わかりません。

アロケータオブジェクトにそのメモリプールが直接含まれないことが保証されていますか(または少なくとも合理的な仮定)、したがって、アロケータをコピーして、アロケータのメモリプールが相互互換性があることを期待できますか?または、常に参照によってアロケータを渡す必要がありますか?

(コンパイラーがエイリアシングについて心配し始めるので、参照を渡すとパフォーマンスが2倍以上低下することがわかりました。そのため、この仮定に頼ることができるかどうかがわかります。)

4

2 に答える 2

11

C ++ 11セクション17.6.3.5では、アロケータ要件[allocator.requirements]は、準拠するアロケータの要件を指定します。要件には次のものがあります。

X                    an Allocator class for type T
...
a, a1, a2            values of type X&
...
a1 == a2             bool          returns true only if storage
                                   allocated from each can be
                                   deallocated via the other.
                                   operator== shall be reflexive,
                                   symmetric, and transitive, and
                                   shall not exit via an exception.
...
X a1(a);                           Shall not exit via an exception.
                                   post: a1 == a

つまり、アロケータをコピーする場合、互いのポインタを削除できるようにするには、2つのコピーが必要です。

おそらく、内部バッファをアロケータに入れることもできますが、コピーは他のバッファのリストを保持する必要があります。あるいは、ポインターが常に内部バッファー(独自のバッファーまたは他のコピーのいずれか)から取得されるため、アロケーターが常に割り当て解除がノーオペレーションであるという不変条件を持つ可能性があります。

ただし、スキームが何であれ、コピーは「相互互換性」がなければなりません。

アップデート

これは、「短い文字列の最適化」を行うC++11準拠のアロケータです。C ++ 11に準拠させるには、コピーが等しくなるように、アロケータの外部に「内部」バッファを配置する必要がありました。

#include <cstddef>

template <std::size_t N>
class arena
{
    static const std::size_t alignment = 16;
    alignas(alignment) char buf_[N];
    char* ptr_;

    std::size_t 
    align_up(std::size_t n) {return n + (alignment-1) & ~(alignment-1);}

public:
    arena() : ptr_(buf_) {}
    arena(const arena&) = delete;
    arena& operator=(const arena&) = delete;

    char* allocate(std::size_t n)
    {
        n = align_up(n);
        if (buf_ + N - ptr_ >= n)
        {
            char* r = ptr_;
            ptr_ += n;
            return r;
        }
        return static_cast<char*>(::operator new(n));
    }
    void deallocate(char* p, std::size_t n)
    {
        n = align_up(n);
        if (buf_ <= p && p < buf_ + N)
        {
            if (p + n == ptr_)
                ptr_ = p;
        }
        else
            ::operator delete(p);
    }
};

template <class T, std::size_t N>
class stack_allocator
{
    arena<N>& a_;
public:
    typedef T value_type;

public:
    template <class U> struct rebind {typedef stack_allocator<U, N> other;};

    explicit stack_allocator(arena<N>& a) : a_(a) {}
    template <class U>
        stack_allocator(const stack_allocator<U, N>& a)
            : a_(a.a_) {}
    stack_allocator(const stack_allocator&) = default;
    stack_allocator& operator=(const stack_allocator&) = delete;

    T* allocate(std::size_t n)
    {
        return reinterpret_cast<T*>(a_.allocate(n*sizeof(T)));
    }
    void deallocate(T* p, std::size_t n)
    {
        a_.deallocate(reinterpret_cast<char*>(p), n*sizeof(T));
    }

    template <class T1, std::size_t N1, class U, std::size_t M>
    friend
    bool
    operator==(const stack_allocator<T1, N1>& x, const stack_allocator<U, M>& y);

    template <class U, std::size_t M> friend class stack_allocator;
};

template <class T, std::size_t N, class U, std::size_t M>
bool
operator==(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y)
{
    return N == M && &x.a_ == &y.a_;
}

template <class T, std::size_t N, class U, std::size_t M>
bool
operator!=(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y)
{
    return !(x == y);
}

次のように使用できます。

#include <vector>

template <class T, std::size_t N> using A = stack_allocator<T, N>;
template <class T, std::size_t N> using Vector = std::vector<T, stack_allocator<T, N>>;

int main()
{
    const std::size_t N = 1024;
    arena<N> a;
    Vector<int, N> v{A<int, N>(a)};
    v.reserve(100);
    for (int i = 0; i < 100; ++i)
        v.push_back(i);
    Vector<int, N> v2 = std::move(v);
    v = v2;
}

上記の問題のすべての割り当てはarena、サイズが1Kbのローカルから取得されます。このアロケータを値または参照で渡すことができるはずです。

于 2012-07-28T19:06:11.193 に答える
11

古い C++ 標準では、標準準拠のアロケータの要件が定められています。これらの要件には、 がある場合Alloc<T> a, ba == bを使用bして、 で割り当てられたものの割り当てを解除できることが含まれますa。アロケータは基本的にステートレスです。


C++11 では、ステートフルアロケーターがサポートされるようになったため、状況はさらに複雑になりました。オブジェクトをコピーおよび移動する場合、アロケーターが異なる場合にあるコンテナーを別のコンテナーからコピーまたは移動できるかどうか、およびアロケーターがどのようにコピーまたは移動されるかについて、特定の規則があります。

最初に質問に答えてください。いいえ、アロケーターをコピーすることが理にかなっているとは絶対に想定できません。また、アロケーターはコピー可能でさえない可能性があります

この件に関する23.2.1/7は次のとおりです。

特に指定のない限り、この節で定義されているすべてのコンテナーは、アロケーターを使用してメモリを取得します (17.6.3.5 を参照)。これらのコンテナー型のコピー コンストラクターはallocator_traits<allocator_-type>::select_on_container_copy_construction、最初のパラメーターを呼び出してアロケーターを取得します。Move コンストラクターは、移動中のコンテナーに属するアロケーターから、move 構築によってアロケーターを取得します。このようなアロケータのムーブ構築は、例外によって終了しません。これらのコンテナー タイプの他のすべてのコンストラクターは、Allocator&引数 (17.6.3.5)、値の型がコンテナーの値の型と同じアロケーター。[注: コンストラクターの呼び出しがオプションのアロケーター引数のデフォルト値を使用する場合、アロケーター型は値の初期化をサポートする必要があります。—終わりの注] このアロケーターのコピーは、各コンテナー オブジェクトの有効期間中、またはアロケーターが置き換えられるまで、これらのコンストラクターおよびすべてのメンバー関数によって実行されるすべてのメモリ割り当てに使用されます。アロケータは、代入または swap() を介してのみ置き換えることができます。アロケータの置換は allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value、 、 allocator_traits<allocator_type>::propagate_on_container_move_assignment::value、またはallocator_traits<allocator_type>::propagate_on_container_swap::value対応するコンテナ操作の実装内で真です。コンテナーの swap 関数の呼び出しの動作は、スワップされるオブジェクトのアロケーターが等しいかallocator_traits<allocator_type>::propagate_on_container_swap::valuetrue である場合を除き、未定義です。この句で定義されているすべてのコンテナー型で、メンバーget_allocator()は、コンテナーの構築に使用されたアロケーターのコピーを返すか、そのアロケーターが置き換えられている場合は、最新の置き換えのコピーを返します。

概要については、 のドキュメントも参照してくださいstd::allocator_traits

于 2012-07-28T18:38:31.060 に答える