为什么C++20中要移除std::alloctor的construct与destory
引言
第一次遇到这个问题是一次偶然的机会在cpp官网上查std::alloctor时发现的,顿时感到十分疑惑,毕竟前脚刚写了这个东西的好处,后脚就发现人家标准移除了,这不禁让人更加疑惑且好奇,为什么要去掉呢,先分配内存,然后构造,然后析构不是很正常的流程呢,带着这个疑问,逛遍了各大论坛,博客,最后终于在stackoverflow上得到了一个思考方式,在知乎大佬的指点下,这个问题有了一些进展,遂进行记录,对有同样疑问的朋友给出一个思考的方向.
开门见山,我们先来看一段社区对于这个问题的看法
Many members of std::allocator redundantly duplicate behavior that is otherwise produced by std::allocator_traits<allocator>, and could safely be removed to simplify this class. Further, addressof as a free function supersedes std::allocator::address which requires an allocator object of the right type. Finally, the reference type aliases were initially provided as an expected means for extension with other allocators, but turned out to not serve a useful purpose when we specified the allocator requirements (17.6.3.5 [allocator.requirements]).
While we cannot remove these members without breaking backwards compatibility with code that explicitly used this allocator type, we should not be recommending their continued use. If a type wants to support generic allocators, it should access the allocator’s functionality through allocator_traits rather than directly accessing the allocator’s members - otherwise it will not properly support allocators that rely on the traits to synthesize the default behaviors. Similarly, if a user does not intend to support generic allocators, then it is much simpler to directly invoke new, delete, and assume the other properties of std::allocator such as pointer-types directly.
很多std::alloctor提供的行为与std::allocator_traits<allocator< T>>中出现了重复,可以安全的删除它们以简化std::alloctor. addressof作为一个自由的函数,其可以替代std::allocator< T>::address,后者需要一个正确类型的alloctor. 还有,最初支持的引用类型别名被提供以与其他分配器一起进行扩展,但是结果是当我们指定了一个分配器需求时并没有提供一个有效的帮助.
尽管我们不能删除掉这些成员为了保证明确使用这些分配器类型的代码的向后兼容性,我们不应该建议继续使用.如果一个类型想支持通用的迭代器,它应该通过allocator_traits使用alloctor的功能而不是通过其成员函数,否则就无法正确的使用依靠traits来合成的默认行为,类似的,如果一个用户不趋于使用通用分配器,他就应该简单的调用new,delete,或者直接假设其他std::alloctor的属性类似于pointer-type.
The allocator requirements table says that construct(c, args), if provided, must “construct an object of type C at c”.
It says absolutely nothing about 1) what arguments are to be passed to C’s constructor or 2) how these arguments are to be passed. That’s the allocator’s choice, and in fact two allocators in the standard do mess with the arguments before passing them to C’s constructor: std::scoped_allocator_adaptor and std::pmr::polymorphic_allocator. When constructing a std::pair, in particular, the arguments they pass to pair’s constructor may not even resemble the ones they received.
There’s no requirement to perfectly forward, either; a C++03-style construct(T*, const T&) is conforming if inefficient.
std::allocator’s construct and destroy are deprecated because they are useless: no good C++11 and later code should ever call them directly, and they add nothing over the default.
allocator requirements table 说construct(c, args)如果存在的话就必须构造在c处构造C对象,
其显然没有提到以下两点 1)什么参数会被传递给C的构造函数 2)这些参数该如何被传递. 这是构造器的选择,事实上两个标准构造器会在传递给C的构造函数是弄乱参数,这就是: std::scoped_allocator_adaptor and std::pmr::polymorphic_allocator.当构造一个std::pair时,特殊的,它们传给pair构造函数的参数可能与它们接收到的不相似.
这并不是完美转发的问题,如果效率不高可使用 a C++03风格 construct(T*, const T&),std::alloctor的construct和的destroy被弃用是因为它们没什么用,好的C++代码应该在C++11或以后的版本直接使用它们,而且它们未添加任何的默认值,
Allocator-aware containers must use allocator_traits::construct, which defers to the allocator’s construct if it exists and supplies a default implementation otherwise. Since std::allocator::construct uses exactly the default implementation, there’s no point in providing it.
Allocator意识到必须使用allocator_traits::construct,如果alloctor的构造函数存在的话就使用,否则提供默认实现,由于std::alloctor::construct完全使用默认实现,所以没有理由使用它.
其实总结下以上观点就是一句话: 功能重复,所以没有必要保留这些成员函数,
allocator_traits::construct的内部其实就是使用了一个placement new来进行构造,在有了分配的内存以后其实与默认的成员construct没有什么区别,
下面是知乎上一位前辈的观点,从支持constexper的容器的方面回答这个问题,:
为什么 allocator traits 要有两种不同的构造策略,因为从 C++17 之后开始,有些 allocator 开始有内部状态了,不再是一个空类型,比如 std::pmr 命名空间下的容器用的 allocator,对于这些类而言,当然在构造时,需要有 allocator 对象参与在构造过程中,所以就要去调这个 allocator 的方法,而对于没有内部状态的 allocator,直接调 placement_new 就 ok 了,c++20 为了实现 constexpr 的 vector,必须要能实现一个能 constexpr 的调用构造函数的设施,这本来应该是 placement new 来完成的,但貌似标准委员会并没有允许 placement new 是 constexpr 的,单独给 std::allocator 开洞也是一种方案,但太不优雅了,只允许 std::allocator 的 construct 方法是 constexpr 的话,那就意味着其他用户自定义的 allocator 就不能实现 constexpr 了,那就会导致比如 vector<int, MyAllocator> 这样的类不能支持 constexpr.
最后感谢这位前辈的指点!
参考:
https://stackoverflow.com/questions/39414610/why-are-are-stdallocators-construct-and-destroy-functions-deprecated-in-c17
https://www.bfilipek.com/2019/03/cpp17indetail-done.html