33

私は Howard Hinnant のスタック アロケータを使用してきましたが、これは魅力的に機能しますが、実装の詳細が少しわかりません。

  1. グローバル演算子newandがdelete使用されるのはなぜですか? allocate()anddeallocate()メンバー関数は、それぞれ and を使用し::operator newます::operator delete。同様に、メンバー関数construct()はグローバル配置 new を使用します。ユーザー定義のグローバルまたはクラス固有のオーバーロードを許可しないのはなぜですか?
  2. アライメントが ではなく、ハードコードされた 16 バイトに設定されているのはなぜstd::alignment_of<T>ですか?
  3. コンストラクタと例外仕様max_sizeがあるのはなぜですか? throw()これはお勧めできませんか (たとえば、より効果的な C++ 項目 14 を参照)。アロケーターで例外が発生したときに終了して中止する必要は本当にありますか? これは新しい C++11noexceptキーワードで変わりますか?
  4. メンバー関数は、construct()(呼び出されているコンストラクターへの) 完全な転送の理想的な候補です。これは C++11 準拠のアロケータを記述する方法ですか?
  5. 現在のコードを C++11 に準拠させるには、他にどのような変更が必要ですか?
4

1 に答える 1

43

私は Howard Hinnant のスタック アロケータを使用してきましたが、これは魅力的に機能しますが、実装の詳細が少しわかりません。

お役に立ててよかったです。

1. グローバル演算子newおよびがdelete使用されるのはなぜですか? allocate()anddeallocate()メンバー関数は、それぞれ and を使用し::operator newます ::operator delete。同様に、メンバー関数 construct()はグローバル配置 new を使用します。ユーザー定義のグローバルまたはクラス固有のオーバーロードを許可しないのはなぜですか?

特に理由はありません。最適な方法でこのコードを自由に変更してください。これはあくまで一例であり、完全ではありません。唯一の要件は、アロケータとデアロケータが適切にアラインされたメモリを提供することと、コンストラクト メンバーが引数を構築することです。

C++11 では、construct (および destroy) メンバーはオプションです。を提供する環境で操作している場合は、アロケーターからそれらを削除することをお勧めしますallocator_traits。調べるには、それらを削除して、まだコンパイルできるかどうかを確認してください。

2. アラインメントが ではなく、ハードコードされた 16 バイトに設定されているのはなぜstd::alignment_of<T>ですか?

std::alignment_of<T>おそらくうまくいくでしょう。その日、私はおそらく妄想的だったでしょう。

3. コンストラクタと例外仕様max_sizeがあるのはなぜですか? throw()これはお勧めできませんか (たとえば、より効果的な C++ 項目 14 を参照)。アロケータで例外が発生したときに終了して中止する必要は本当にありますか? これは新しい C++11 noexceptキーワードで変わりますか?

これらのメンバーは決してスローしません。C++11 の場合、それらを に更新する必要がありnoexceptます。C++11 ではnoexcept、特に特別なメンバーで物事を装飾することがより重要になります。C++11 では、式が非スローかどうかを検出できます。コードはその答えに応じて分岐できます。スローされないことがわかっているコードは、一般的なコードをより効率的なパスに分岐させる可能性が高くなります。 std::move_if_noexceptC++11 の標準的な例です。

決して使わないでくださいthrow(type1, type2)。C++11 では廃止されました。

throw()本当に言いたいときに 使用してください。これは決してスローされません。間違っている場合は、デバッグできるようにプログラムを終了してください。throw()も C++11 で廃止されましたが、ドロップイン置換があります: noexcept.

4.construct()メンバー関数は、(呼び出されているコンストラクターへの) 完全転送の理想的な候補です。これは C++11 準拠のアロケータを記述する方法ですか?

はい。ただしallocator_traits、あなたのためにそれを行います。させてください。std::lib はすでにそのコードをデバッグしています。C++11 コンテナーは を呼び出しますallocator_traits<YourAllocator>::construct(your_allocator, pointer, args...)。アロケータがこれらの関数を実装している場合、 allocator_traits は実装を呼び出します。それ以外の場合は、デバッグ済みの効率的なデフォルトの実装を呼び出します。

5. 現在のコードを C++11 に準拠させるには、他にどのような変更が必要ですか?

実を言うと、このアロケータは実際には C++03 または C++11 に準拠していません。アロケータをコピーするとき、オリジナルとコピーは互いに等しいはずです。このデザインでは、それは決して真実ではありません。ただし、これはまだたまたま多くのコンテキストで機能します。

厳密に準拠させたい場合は、コピーが同じバッファーを指すように、別のレベルの間接化が必要です。

それとは別に、C++11 アロケーターはC++98/03 アロケーターよりもビルドはるかに簡単です。最低限しなければならないことは次のとおりです。

template <class T>
class MyAllocator
{
public:
    typedef T value_type;

    MyAllocator() noexcept;  // only required if used
    MyAllocator(const MyAllocator&) noexcept;  // copies must be equal
    MyAllocator(MyAllocator&&) noexcept;  // not needed if copy ctor is good enough
    template <class U>
        MyAllocator(const MyAllocator<U>& u) noexcept;  // requires: *this == MyAllocator(u)

    value_type* allocate(std::size_t);
    void deallocate(value_type*, std::size_t) noexcept;
};

template <class T, class U>
bool operator==(const MyAllocator<T>&, const MyAllocator<U>&) noexcept;

template <class T, class U>
bool operator!=(const MyAllocator<T>&, const MyAllocator<U>&) noexcept;

必要に応じてMyAllocatorSwappable を作成し、次のネストされた型をアロケーターに配置することを検討してください。

typedef std::true_type propagate_on_container_swap;

C++11 アロケーターで微調整できるようなノブが他にもいくつかあります。しかし、すべてのノブには適切なデフォルトがあります。

アップデート

上記で、コピーが等しくないという事実のために、スタック アロケータが準拠していないことに注意してください。このアロケーターを C++11 準拠のアロケーターに更新することにしました。新しいアロケータはshort_allocatorと呼ばれ、ここに文書化されています。

short_allocatorは、"内部" バッファーがアロケーターの内部ではなく、ローカル スタック上に配置できる個別の "アリーナ" オブジェクトであるという点で、スタック アロケーターとは異なります。ただし、arenaスレッドセーフではないので注意してください。必要に応じてスレッドセーフにすることもできますが、それでは利益が減少します (最終的には malloc を再発明することになります)。

アロケータのコピーはすべて同じ external を指しているため、これは準拠していますarenaNの単位が の数ではなくバイトになっていることに注意してくださいT

C++98/03 ボイラープレート (typedef、construct メンバー、destroy メンバーなど) を追加することで、この C++11 アロケーターを C++98/03 アロケーターに変換できます。面倒ですが、簡単な作業です。

新しいshort_allocatorに関するこの質問への回答は変更されていません。

于 2012-07-25T14:48:35.560 に答える