嵌套容器的分配器

问题描述 投票:0回答:1

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;
}

我想这里的一般主题是关于“嵌套容器中的分配器”。虽然从功能上来说,我明白了这个问题,但我无法理解代码中发生了什么。

在代码中,我们有一个自定义的分配器,本质上,它的行为类似于默认分配器,只是它在内部存储一种数据。

我使用同一分配器的三个不同实例构建三个不同的字符串:

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
关联的分配器是对象本身的一部分,不是吗?

c++ nested allocator
1个回答
0
投票

这是分配器水平传播的结果,由容器根据POCCA、POCMA和POCS分别执行复制分配、移动分配和交换操作。

在您的示例中,

insert()
成员函数移动两个已经存在的元素以释放新元素的第一个位置。该过程可以总结为以下方案。

  1. str2
    对象是在数组末尾移动构造的,使原始对象处于有效但未定义的状态(
    *str2
    )。在移动构造期间,分配器也会移动。值得注意的是,移出的分配器保证与移至的分配器相等,因此
    *str2
    对象仍然包含其原始分配器。
  +-----------------------------------
  | str1(a1) | *str2(a2) | str2(a2) |
  +-----------------------------------
  1. str1
    对象被移动分配给
    *str2
    对象。容器通过
    propagate_on_container_move_assignment
    接口检查
    std::allocator_traits
    类型特征。由于分配器不提供这样的类型,因此该类将选择其默认类型,该类型的计算结果为 false。这意味着分配器无法传播到目标对象,必须采用其分配器。
  +-----------------------------------
  | *str1(a1) | str1(a2) | str2(a2) |
  +-----------------------------------
  1. str1
    对象被移动分配给
    *str2
    对象。与上一步类似,容器通过
    propagate_on_container_copy_assignment
    接口检查
    std::allocator_traits
    类型特征。由于分配器不提供这样的类型,因此该类将选择其默认类型,该类型的计算结果为 false。这意味着分配器无法传播到目标对象,必须采用其分配器。
  +----------------------------------
  | str3(a1) | str1(a2) | str2(a2) |
  +----------------------------------
© www.soinside.com 2019 - 2024. All rights reserved.