Własny alokator c++, błąd przepisania do std::string

0

Cześć!

Usiłuję zmusić swoją klasę alokatora do np. stringów by w końcu ruszyła.

Wrzucam poniżej wersję allocator z zmienioną nazwą na dev_allocator.

#include <iostream>
#include <string>

using namespace std;

template<typename _Tp>
class dev_allocator
{
public:
    typedef size_t     size_type;
    typedef ptrdiff_t  difference_type;
    typedef _Tp*       pointer;
    typedef const _Tp* const_pointer;
    typedef _Tp&       reference;
    typedef const _Tp& const_reference;
    typedef _Tp        value_type;

    template<typename _Tp1>
      struct rebind
      { typedef dev_allocator<_Tp1> other; };

#if __cplusplus >= 201103L
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 2103. propagate_on_container_move_assignment
    typedef std::true_type propagate_on_container_move_assignment;
#endif

    dev_allocator() _GLIBCXX_USE_NOEXCEPT { }

    dev_allocator(const dev_allocator&) _GLIBCXX_USE_NOEXCEPT { }

    template<typename _Tp1>
      dev_allocator(const dev_allocator<_Tp1>&) _GLIBCXX_USE_NOEXCEPT { }

    ~dev_allocator() _GLIBCXX_USE_NOEXCEPT { }

    pointer
    address(reference __x) const _GLIBCXX_NOEXCEPT
    { return std::__addressof(__x); }

    const_pointer
    address(const_reference __x) const _GLIBCXX_NOEXCEPT
    { return std::__addressof(__x); }

    // NB: __n is permitted to be 0.  The C++ standard says nothing
    // about what the return value is when __n == 0.
    pointer
    allocate(size_type __n, const void* = 0)
    {
  if (__n > this->max_size())
    std::__throw_bad_alloc();

  return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
    }

    // __p is not permitted to be a null pointer.
    void
    deallocate(pointer __p, size_type)
    { ::operator delete(__p); }

    size_type
    max_size() const _GLIBCXX_USE_NOEXCEPT
    { return size_t(-1) / sizeof(_Tp); }

#if __cplusplus >= 201103L
    template<typename _Up, typename... _Args>
      void
      construct(_Up* __p, _Args&&... __args)
  { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }

    template<typename _Up>
      void
      destroy(_Up* __p) { __p->~_Up(); }
#endif

};

template<typename _T1, typename _T2>
inline bool operator==(const dev_allocator<_T1>&, const dev_allocator<_T2>&) _GLIBCXX_USE_NOEXCEPT
{
    return true;
}

template<typename _Tp>
inline bool operator==(const dev_allocator<_Tp>&, const dev_allocator<_Tp>&) _GLIBCXX_USE_NOEXCEPT
{
    return true;
}

template<typename _T1, typename _T2>
inline bool operator!=(const dev_allocator<_T1>&, const dev_allocator<_T2>&) _GLIBCXX_USE_NOEXCEPT
{
    return false;
}

template<typename _Tp>
inline bool operator!=(const dev_allocator<_Tp>&, const dev_allocator<_Tp>&) _GLIBCXX_USE_NOEXCEPT
{
    return false;
}




int main(){
    basic_string<char, char_traits<char>, dev_allocator<char>> dev_str;

    string str = dev_str;
    return 0;
}

Błąd

conversion from 'std::__cxx11::basic_string<char, std::char_traits<char>, dev_allocator<char> >' to non-scalar type 'std::__cxx11::string {aka std::__cxx11::basic_string<char>}' requested
         string str = dev_str;
                      ^~~~~~~

Typowy string to:

basic_string<char, char_traits<char>, allocator<char>>

Więc typy się nie zgadzają i operatory mówią spadaj :P
Jednak z logicznego punktu widzenia (zapominając na chwilę o C++), klasa alokacji nie powinna mieć znaczenia przy kopiowaniu danych.
Jak rozwiązać taki problem? Przeanalizować jak jest kopiowany string do stringa i napisać takie dla klas z alokacją allocator i dev_allocator?
Czy jest jakiś sposób na sprytną specjalizację template?

3

string str(dev_str.begin(), dev_str.end());
Typy się nie zgadzają i już. Nie wiem dlaczego Cię to dziwi.

3

Cały STL zbudowany jest aktualnie w oparciu o niepolimorficzne alokatory. Zatem jeśli zdefiniujesz (w uproszeczniu) kontener<T, AlokatorA> oraz kontener<T, AlokatorB> to masz instancje o różnych typach (bez automatycznej konwersji jednego w drugi). Możesz jednak skorzystać konstruktorów danego kontenera żeby skopiować dane (zaalokować je nowym alokatorem):

std::string str (std::begin(dev_str), std::end(dev_str));

albo przenieść te dane do nowego kontenera:

std::string str (std::make_move_iterator(std::begin(dev_str)), std::make_move_iterator(std::end(dev_str)));

Dodam, że w C++17 pojawi się polimorficzny alokator, który będzie oparty o runtimowy dispatch, a w raz z nim dojdą nowe definicje kontenerów, które będą z niego korzystać. Całość będzie siedzieć w namespace std::pmr, np. std::pmr::string.

Jeszcze jedno, wiele portów STL (np. w game devie) unika jak ognia tego wiązania szablonowego alokatorów. Jeśli cię interesuje jak to wygląda to rzuć okiem np. na implementację EASTL/UE.

0

Dzięki za info, na pewno wszystko poczytam.
Piszę nowy alokator, by alokować stringi, strumienie, listy itp w innym obszarze pamięci, używając mmap z opcją map_shared, tak aby mieć wspólną np. std::map'e w procesach stworzonych forkiem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1