新的现代C ++容器中的分配器传播策略 [英] Allocator propagation policies in your new modern C++ containers

查看:123
本文介绍了新的现代C ++容器中的分配器传播策略的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在容器中具有这些特征的原因是什么( https://en .cppreference.com/w/cpp/memory/allocator_traits )

What is the reason for having these traits in a container (https://en.cppreference.com/w/cpp/memory/allocator_traits)

propagate_on_container_copy_assignment  Alloc::propagate_on_container_copy_assignment if present, otherwise std::false_type
propagate_on_container_move_assignment  Alloc::propagate_on_container_move_assignment if present, otherwise std::false_type
propagate_on_container_swap             Alloc::propagate_on_container_swap if present, otherwise std::false_type

is_always_equal(since C++17)            Alloc::is_always_equal if present, otherwise std::is_empty<Alloc>::type

我知道容器的实现在分配和交换的实现中会以一种或另一种方式表现. (并且处理这种情况是很糟糕的代码.) 我也了解有时可能需要将move-from容器保持为resizeble的状态,或者至少可以调用一些最后的释放,所以分配器不会无效. (我个人认为这是一个微不足道的论点.)

I understand that the container implementation will behave in one way or another in their implementation of assignment and swap. (and that handling of these case is horrible code.) I also understand that sometimes one might need to leave the move-from container in a state that is resizeble or that at least some very last deallocation can be called, so the allocator can't be left invalid. (I personally think that is a weak argument.)

但是问题是,为什么信息不能已经成为自定义分配器类型本身的语义的正常实现的一部分?

我的意思是,容器复制分配可以尝试对源分配器进行复制分配,如果语法复制分配没有真正复制,那么,这就像在说您的容器没有 propagate_on_container_copy_assignment.

I mean, container copy-assignment can try copy-assign the source allocator, and if that syntactic copy assign doesn't really copy, then, well, it is like saying that your container doesn't propagate_on_container_copy_assignment.

以相同的方式代替使用is_always_equal实际上可以使分配器分配不执行任何操作.

In the same way instead of using is_always_equal one can actually make the allocator assignment do nothing.

(此外,如果is_always_equal为true,则可以使分配器的operator==返回std::true_type来表明这一点.)

(Besides, if is_always_equal is true one can make operator== for allocators return std::true_type to signal that.)

在我看来,这些特征似乎尝试 override 可以通过常规C ++方式赋予自定义分配器的语义.这似乎与通用编程和当前的C ++哲学背道而驰.

It looks to me that these traits seem to try override the semantics that one can give to the custom allocator by normal C++ means. This seems to play against generic programming and the current C++ philosophy.

唯一的原因,我认为这对于实现与旧"容器的某种向后兼容性很有用.

The only reason, I can think of this can be useful to fulfill some kind of backward compatibility with "old" containers.

如果我今天要编写一个 new 容器和/或一个 new 无关紧要的分配器,我可以依靠分配器的语义而忘记了吗关于这些特征?

If I were to write a new container and/or an new non-trivial allocator today, can I rely on the sematics of the allocator and forget about these traits?

在我看来,只要移出的分配器可以释放"一个空指针状态(在这种情况下,这主要意味着什么都不做),那应该没问题,并且如果resize抛出那也很好(有效),它只是意味着分配器不再有权访问其堆.

In my view, as long as the moved-from allocator can "deallocate" a null pointer state (which means mostly to do nothing in this case), then it should be fine, and if resize throws that is fine (valid) too, it simply means that the allocator doesn't have access to its heap anymore.

编辑:实际上,我可以这样简单地编写容器吗?并将复杂性委托给自定义分配器的语义?:

In practical terms, Can I write the containers simply this way? and delegate the complexity to the semantics of the custom allocators?:

templata<class Allocator>
struct my_container{
  Allocator alloc_;
  ...
  my_container& operator=(my_container const& other){
    alloc_ = other.alloc_; // if allocator is_always_equal equal this is ok, if allocator shouldn't propagate on copy, Alloc::operator=(Alloc const&) simply shouldn't do anything in the first place
    ... handle copy...
    return *this;
  }
  my_container& operator=(my_container&& other){
    alloc_ = std::move(other.alloc_); // if allocator shouldn't propagate on move then Alloc::operator=(Alloc&&) simply shouldn't do anything.
    ... handle move...
    return *this;
  }
  void swap(my_container& other){
     using std::swap;
     swap(alloc, other.alloc); //again, we assume that this does the correct thing (including not actually swapping anything if that is the desired criteria. (that would be the case equivalent to `propagate_on_container_swap==std::false_type`)
     ... handle swap...
  }
}

我认为对分配器的唯一真正要求是,从分配器中移出应该能够做到这一点.

I think the only true requirement to an allocator is that, a moved-from allocator should be able to do this.

my_allocator a2(std::move(a1));
a1.deallocate(nullptr, 0); // should ok, so moved-from container is destructed (without exception)
a1.allocate(n); // well defined behavior, (including possibly throwing bad_alloc).

推荐答案

Nicol Bolas的答案非常准确.我会这样说:

Nicol Bolas's answer is very accurate. I would say it like this:

  • 分配器是堆的句柄.这是一个值-语义类型,就像指针或intstring一样.复制分配器时,将获得其值的副本.副本比较相等.这适用于分配器,就像它适用于指针或int s或string s一样.

  • An allocator is a handle to a heap. It's a value-semantic type, just like a pointer or an int or a string. When you copy an allocator, you get a copy of its value. Copies compare equal. This works for allocators just like it works for pointers or ints or strings.

使用分配器可以做的一件事是使用纯值语义将其传递给不同的算法和数据结构. STL在这个部门中没有很多,但确实有 allocate_shared .

One thing you can do with an allocator is pass it around to different algorithms and data structures using pure value semantics. The STL doesn't have much in this department, but it does have e.g. allocate_shared.

使用分配器可以完成的另一件事是将其分配给STL容器.您在容器构造期间将分配器分配给容器.在其生命周期的某些时候,容器会遇到其他分配器,因此必须做出选择.

Another thing you can do with an allocator is give it to an STL container. You give the allocator to the container during construction of the container. At certain points during its lifetime, the container will encounter other allocators, and it will have to make a choice.

A<int> originalAlloc = ...;
std::vector<int, A<int>> johnny(originalAlloc);

A<int> strangeAlloc = ...;
std::vector<int, A<int>> pusher(strangeAlloc);

// pssst kid wanna try my allocator? it'll make you feel good
johnny = std::move(pusher);


此时,johnny必须做出一个艰难的决定:就我的 value 而言,我正在采用pusher的元素的值;我是否也应采用他的分配器?"


At this point, johnny has to make a tough decision: "I'm adopting pusher's elements' values as far as my value is concerned; should I also adopt his allocator?"

在c ++ 11及更高版本中,johnny做出决定的方式是咨询allocator_traits<A<int>>::propagate_on_container_move_assignment并执行其指示:如果显示true,则我们将采用strangeAlloc,并且如果它表示false,我们将坚持我们的原则,并坚持我们原来的分配器.坚持使用原始分配器确实意味着我们可能需要做大量额外工作才能复制所有pusher元素(我们不能只是窃取他的数据指针,因为它指向与strangeAlloc相关联的堆) ,而不是与originalAlloc关联的堆.

The way johnny makes his decision, in C++11-and-later, is to consult allocator_traits<A<int>>::propagate_on_container_move_assignment and do what it says: if it says true then we'll adopt strangeAlloc, and if it says false we'll stick to our principles and stick with our original allocator. Sticking with our original allocator does mean we might have to do a bunch of extra work to make copies of all pusher's elements (we can't just pilfer his data pointer, because it points into the heap associated with strangeAlloc, not the heap associated with originalAlloc).

重点是,决定坚持使用当前的分配器还是采用新的分配器,这一决定仅在容器的情况下才有意义.这就是为什么特征propagate_on_container_move_assignment(POCMA)以及POCCA和POCS的名称都带有容器"的原因.这是关于在容器分配过程中发生的事情,而不是在 allocator 分配过程中发生的事情.分配器分配遵循值语义,因为分配器是值语义类型.期间.

The point is, deciding to stick with your current allocator or adopt a new one is a decision that makes sense only in the context of a container. That's why the traits propagate_on_container_move_assignment (POCMA) and POCCA and POCS all have "container" in the name. It's about what happens during container assignment, not allocator assignment. Allocator assignment follows value semantics, because allocators are value-semantic types. Period.

那么,propagate_on_container_move_assignment(POCMA)和POCCA和POCS都应该是容器类型的属性吗?我们是否应该有std::vector<int>混杂地采用了分配器,而std::stickyvector<int>总是坚持使用了构造它的分配器?好吧,也许.

So, should propagate_on_container_move_assignment (POCMA) and POCCA and POCS all have been attributes of the container type? Should we have had std::vector<int> which promiscuously adopts allocators, and std::stickyvector<int> that always sticks with the allocator it was constructed with? Well, probably.

C ++ 17假装我们做到了 那样,通过提供类似于std::pmr::vector<int>的类型定义(如std::pmr::vector<int>)来做到这一点;但是std::pmr::vector<int>只是std::vector<int, std::pmr::polymorphic_allocator<int>>的typedef,仍然可以通过咨询std::allocator_traits<std::pmr::polymorphic_allocator<int>>来弄清楚该怎么做.

C++17 kind of pretends that we did do it that way, by giving typedefs like std::pmr::vector<int> that look very similar to std::stickyvector<int>; but under the hood std::pmr::vector<int> is just a typedef for std::vector<int, std::pmr::polymorphic_allocator<int>> and still figures out what to do by consulting std::allocator_traits<std::pmr::polymorphic_allocator<int>>.

这篇关于新的现代C ++容器中的分配器传播策略的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆