Распределители для вложенных контейнеров - PullRequest
0 голосов
/ 05 июня 2018

После разговора о распределителях на CppCon я наткнулся на следующий фрагмент кода :

#include <iostream>
#include <string>
#include <utility>
#include <vector>

namespace {

template <typename T>
class MyAllocator {
 public:
  using value_type = T;

  MyAllocator(std::string iType) : _type(std::move(iType)) {}

  T* allocate(const std::size_t iNo) { return new T[iNo]; }
  void deallocate(T* iPtr, const std::size_t) { delete[] iPtr; }

  constexpr bool operator!=(const MyAllocator& oth) const {
    return _type != oth._type;
  }

  const std::string& getType() const noexcept { return _type; }

 private:
  std::string _type;
};

using MyString =
    std::basic_string<char, std::char_traits<char>, MyAllocator<char>>;

}  // anonymous namespace

int main(int, char**) {
  ::MyString str1(::MyAllocator<char>("ForStr1"));
  ::MyString str2(::MyAllocator<char>("ForStr2"));
  ::MyString str3(::MyAllocator<char>("ForStr3"));

  std::vector<::MyString> aVector;
  aVector.reserve(1024);

  aVector.push_back(str1);
  aVector.push_back(str2);

    std::cout << "[0]: " << aVector[0].get_allocator().getType() << "\n"
              << "[1]: " << aVector[1].get_allocator().getType() << "\n";


  aVector.insert(aVector.begin(), str3);

  const auto& type0 = aVector[0].get_allocator().getType();
  const auto& type1 = aVector[1].get_allocator().getType();
  const auto& type2 = aVector[2].get_allocator().getType();

  std::cout << "[0]: " << type0 << "\n"
            << "[1]: " << type1 << "\n"
            << "[2]: " << type2 << "\n";

  return 0;
}

Я думаю,общая тема здесь о " распределителях во вложенных контейнерах ".Хотя с функциональной точки зрения у меня возникает проблема, я не могу понять, что произошло в коде.

В коде у нас есть пользовательский allocator , который, по сути, ведет себя как распределитель по умолчанию, за исключением еговнутренне хранит вид данных.

Я строю три разных строк с тремя различными экземплярами одного и того же распределителя:

using MyString = 
    std::basic_string<char, std::char_traits<char>, MyAllocator<char>>;

::MyString str1(::MyAllocator<char>("ForStr1"));
::MyString str2(::MyAllocator<char>("ForStr2"));
::MyString str3(::MyAllocator<char>("ForStr3"));

Теперь яиметь простое std::vector<MyString>:

std::vector<::MyString> aVector;
aVector.reserve(1024);

Я зарезервировал пространство, чтобы избежать перераспределение .

Теперь я нажимаю первые две строки:

aVector.push_back(str1);
aVector.push_back(str2);

std::cout << "[0]: " << aVector[0].get_allocator().getType() << "\n"
          << "[1]: " << aVector[1].get_allocator().getType() << "\n";

// As expected, it prints:
//       [0]: ForStr1
//       [1]: ForStr2

Результат печати - то, что я ожидаю.Я предполагаю, что распределитель принадлежит контейнеру std::string.

Однако , если я принудительно выполню некоторые операции копирования / перемещения (перестановки) с помощью:

aVector.insert(aVector.begin(), str3);

// Now we have vector be like:
//  [str3:ForStr3]    [str1:ForStr1]    [str2:ForStr2]

Затем,распределители, связанные со строками внутри вектора, кажутся поврежденными:

const auto& type0 = aVector[0].get_allocator().getType();
const auto& type1 = aVector[1].get_allocator().getType();
const auto& type2 = aVector[2].get_allocator().getType();

std::cout << "[0]: " << type0 << "\n"
          << "[1]: " << type1 << "\n"
          << "[2]: " << type2 << "\n";

Он печатает:

[0]: ForStr1
[1]: ForStr2
[2]: ForStr2

Я ожидаю:

[0]: ForStr3
[1]: ForStr1
[2]: ForStr2

Почему это поведение?Есть ли UB , которые я пропустил?Распределитель, связанный с std::string, является частью самого объекта, не так ли?

...