ユーザー提供のstd :: allocator特殊化


8

::std名前空間内のクラステンプレートは、通常、ユーザー定義型のプログラムに特化できます。私はこのルールに例外を見つけませんでしたstd::allocator

それで、std::allocator私は自分のタイプに特化することを許可されていますか?また、許可されている場合、std::allocatorの主なテンプレートのすべてのメンバーを提供する必要がありstd::allocator_traitsますか?

このプログラムを検討してください

#include<vector>
#include<utility>
#include<type_traits>
#include<iostream>
#include<limits>
#include<stdexcept>

struct A { };

namespace std {
    template<>
    struct allocator<A> {
        using value_type = A;
        using size_type = std::size_t;
        using difference_type = std::ptrdiff_t;
        using propagate_on_container_move_assignment = std::true_type;

        allocator() = default;

        template<class U>
        allocator(const allocator<U>&) noexcept {}

        value_type* allocate(std::size_t n) {
            if(std::numeric_limits<std::size_t>::max()/sizeof(value_type) < n)
                throw std::bad_array_new_length{};
            std::cout << "Allocating for " << n << "\n";
            return static_cast<value_type*>(::operator new(n*sizeof(value_type)));
        }

        void deallocate(value_type* p, std::size_t) {
            ::operator delete(p);
        }

        template<class U, class... Args>
        void construct(U* p, Args&&... args) {
            std::cout << "Constructing one\n";
            ::new((void *)p) U(std::forward<Args>(args)...);
        };

        template<class U>
        void destroy( U* p ) {
            p->~U();
        }

        size_type max_size() const noexcept {
            return std::numeric_limits<size_type>::max()/sizeof(value_type);
        }
    };
}

int main() {
    std::vector<A> v(2);
    for(int i=0; i<6; i++) {
        v.emplace_back();
    }
    std::cout << v.size();
}

libc ++(Clang with -std=c++17 -Wall -Wextra -pedantic-errors -O2 -stdlib=libc++)を使用したこのプログラムの出力は次のとおりです。

Allocating for 2
Constructing one
Constructing one
Allocating for 4
Constructing one
Constructing one
Allocating for 8
Constructing one
Constructing one
Constructing one
Constructing one
8

そして、libstdc ++(Clang with -std=c++17 -Wall -Wextra -pedantic-errors -O2 -stdlib=libstdc++)での出力は次のとおりです。

Allocating for 2
Allocating for 4
Constructing one
Constructing one
Allocating for 8
Constructing one
Constructing one
Constructing one
Constructing one
8

あなたがにlibstdc見ることができるように++いつもの過負荷尊重していないconstruct私が提供していることを、私は削除した場合constructdestroyまたはmax_sizeメンバーを彼らはによって供給されるが、その後、プログラムはさらに、これらの不足しているメンバーに文句のlibstdc ++でコンパイルされませんstd::allocator_traits

プログラムには未定義の動作があるため、両方とも標準ライブラリは正しいですか、それともプログラムの動作は明確に定義されており、標準化ライブラリは私の専門分野を使用するために必要ですか?


std::allocator自分の専門分野でまだ除外したのプライマリテンプレートのメンバーがいることに注意してください。それらも追加する必要がありますか?

正確には省略しました

using is_always_equal = std::true_type

これはstd::allocator_traits、私のアロケータが空なので提供されますが、std::allocatorのインターフェイスの一部になります。

私はまた取り残さpointerconst_pointerreferenceconst_referencerebindaddress、によって提供されているすべてはstd::allocator_traitsとのためのC ++ 17には非推奨std::allocatorのインタフェース。

std::allocatorのインターフェースに一致するようにこれらすべてを定義する必要があると思われる場合は、コードに追加されていると考えてください。


私はこれをすべて取り戻します。私はVisual Studio 2019で試してみましたが、すべてのコンストラクター呼び出し(コピーコンストラクター呼び出しを含む)があります。。それにもかかわらず、libstdc ++はオプティマイザが削除できると考える方法でそれを実装していると思います。
スペンサー

回答:


3

23.2.1 [container.requirements.general] / 3によれば:

を宣言するこの副次句の影響を受けるコンポーネントの場合、allocator_typeこれらのコンポーネントに格納されているオブジェクトは、allocator_traits<allocator_type>::construct関数を使用して構築されます。

また、17.6.4.2.1によれば、

プログラムはstd、宣言がユーザー定義型に依存し、特殊化が元のテンプレートの標準ライブラリ要件を満たし、明示的に禁止されていない場合にのみ、標準ライブラリテンプレートの特殊化テンプレートを名前空間に追加できます。

標準が専門化を禁止しているとは思わない std::allocatorすべてのセクションを読み、std::allocator何も言及していなかったので。また、規格が専門化を禁止するのがどのように見えるかを調べたところ、そのようなものは見つかりませんでしたstd::allocator

の要件 Allocatorここにあり、あなたの専門分野はそれらを満たしています。

したがって、私はlibstdc ++が実際に標準に違反していると結論付けることができるだけです(おそらく、どこかで間違いを犯したかもしれません)。std::allocator他の操作に指定されたアロケーターを使用しながら、このケース専用のテンプレート特殊化を持っているため、単にlibstdc ++がコンストラクターの配置newを使用して応答することがわかりました。関連するコードはここにあります(これはにありnamespace stdます。allocatorここにあります)::std::allocator):

  // __uninitialized_default_n_a
  // Fills [first, first + n) with n default constructed value_types(s),
  // constructed with the allocator alloc.
  template<typename _ForwardIterator, typename _Size, typename _Allocator>
    _ForwardIterator
    __uninitialized_default_n_a(_ForwardIterator __first, _Size __n, 
                _Allocator& __alloc)
    {
      _ForwardIterator __cur = __first;
      __try
    {
      typedef __gnu_cxx::__alloc_traits<_Allocator> __traits;
      for (; __n > 0; --__n, (void) ++__cur)
        __traits::construct(__alloc, std::__addressof(*__cur));
      return __cur;
    }
      __catch(...)
    {
      std::_Destroy(__first, __cur, __alloc);
      __throw_exception_again;
    }
    }

  template<typename _ForwardIterator, typename _Size, typename _Tp>
    inline _ForwardIterator
    __uninitialized_default_n_a(_ForwardIterator __first, _Size __n, 
                allocator<_Tp>&)
    { return std::__uninitialized_default_n(__first, __n); }

std::__uninitialized_default_n 呼び出し std::_Construct新しい配置を使用する。これが、出力で「4の割り当て」の前に「1を構築しています」が表示されない理由を説明しています。

編集: OPがコメントで指摘したように、std::__uninitialized_default_n通話

__uninitialized_default_n_1<__is_trivial(_ValueType)
                             && __assignable>::
__uninit_default_n(__first, __n)

if __is_trivial(_ValueType) && __assignableは実際に特化しtrueています。これは、各要素を呼び出すのではなく、std::fill_nvalue簡単に構築される場所)を使用しstd::_Constructます。Aはささいなことでコピー割り当てが可能なので、実際にはこの特殊化を呼び出すことになります。もちろん、これも使いませんstd::allocator_traits<allocator_type>::construct


1
私の特定のケースでは、実際に使用されていないstd::_Constructので、私が言うことができるものから電話をA自明コピー可能であると自明のため、他の専門アサイナブルコピー__uninitialized_default_n_1呼び出して選択され、std::fill_nその代わりに、どの再び派遣にmemcpy/ memset。libstdc ++が行う最適化としてこれを認識していましたが、std::allocator::construct重要な型の呼び出しもスキップされることを知りませんでした。したがって、libstdc ++がライブラリ提供のものstd::allocatorが使用されていることを識別する方法の見落としの可能性があります。
クルミ

あなたが正しい。編集。
レオニード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.