在23.2.1p3 C ++ 11 Standart中,我们可以阅读:
对于受本子条款影响的声明
allocator_type
的组件,存储在这些组件中的对象应使用allocator_traits<allocator_type>::construct
函数构造,并使用allocator_traits<allocator_type>::destroy
函数销毁(20.6.8.2) 。这些函数仅针对容器的元素类型调用,而不是针对容器使用的内部类型调用。 [注意:这意味着,例如,基于节点的容器可能需要构造包含对齐缓冲区的节点,并调用construct
将元素放入缓冲区。 - 结束记录]
allocator_traits<allocator_type>::construct
只调用传递的allocator的construct
方法,如果allocator定义了一个方法。我尝试使用它并创建分配器,它使用列表初始化进行构造,因此我可以利用emplace
进行聚合初始化:
#include <memory>
#include <vector>
#include <string>
#include <iostream>
#include <cmath>
template<typename T>
struct init_list_allocator : public std::allocator<T> {
template<typename... Args>
void construct(T* p, Args&&... args)
{ ::new((void *)p) T{std::forward<Args>(args)...}; }
// Fix copy-constructors usage for aggregates
void construct(T* p, T& copy_construct_arg)
{ std::allocator<T>::construct(p, copy_construct_arg); }
void construct(T* p, const T& copy_construct_arg)
{ std::allocator<T>::construct(p, copy_construct_arg); }
void construct(T* p, const T&& copy_construct_arg)
{ std::allocator<T>::construct(p, std::move(copy_construct_arg)); }
void construct(T *p, T&& move_construct_arg)
{ std::allocator<T>::construct(p, std::move(move_construct_arg)); }
};
template<class T>
using improved_vector = std::vector<T, init_list_allocator<T>>;
struct A {
int x;
double y;
const char* z;
};
int main()
{
using namespace std;
vector<string> strings;
improved_vector<A> v;
for (int i = 0; i < 21; ++i) {
strings.emplace_back(to_string(i*i));
v.emplace_back(i, sqrt(i), strings.back().c_str());
};
for (const auto& elem : v)
cout << elem.x << ' ' << elem.y << ' ' << elem.z << '\n';
}
但是,至少在gcc和clang中,这不起作用。问题是,vector
的实现使用Allocator::rebind<T>::other::construct
而不是Allocator::construct
。而且,由于我们从std::allocator
继承,因此rebind
给出std::allocator<T>::construct
。好的,没问题,只需添加
template<typename U>
struct rebind {
using other = init_list_allocator<U>;
};
在我们的分配器定义中,此代码将起作用。很好,现在让我们将vector
更改为list
。这里我们有无法解决的问题,因为而不是Allocator::construct
对象在std::_List_node<_Tp>
构造函数中以直接初始化形式(带括号的形式)初始化。
这2个问题是违规行为还是我错过了什么?
答案 0 :(得分:1)
据我了解,libstdc ++和MSVC ++在这里是正确的。正如注释所示,rebind
的要点是,可能需要容器来构造非T
的东西。例如,std::list<T>
需要构建一个包含T
而非T
的列表节点。类似的情况存在于关联和无序容器中。这就是rebind
结构首先存在的原因。在此之前,你的分配器是不合格的。
对于第二个问题,您的参考
这些函数仅针对容器的元素类型调用,而不是针对容器使用的内部类型调用。
似乎表明标准库实现不允许为反弹分配器调用construct
。这可能是libstdc ++中的一个错误。
至于这个问题的实际解决方案,给A
一个具有你想要的行为的构造函数,并且为此目的不要为分配器而烦恼。人们可能希望使用特殊分配器在容器外部创建A
的实例:
#include <vector>
struct A {
int x;
double y;
const char* z;
A() = default; // This allows A to still be a POD because the default constructor
// is not "user-provided", see 8.4.2 [dcl.fct.def.default]/4
A(int x_, double y_, char const* z_) : x(x_), y(y_), z(z_) {}
};
int main()
{
using namespace std;
vector<string> strings;
vector<A> v;
for (int i = 0; i < 21; ++i) {
strings.emplace_back(to_string(i*i));
v.emplace_back(i, sqrt(i), strings.back().c_str());
};
for (const auto& elem : v)
cout << elem.x << ' ' << elem.y << ' ' << elem.z << '\n';
}
答案 1 :(得分:0)
经过一些研究,我自己找到了答案并希望提供答案。
对于第一个问题(使用Allocator::rebind<T>::other::construct
而不是Allocator::construct
),我的第一个分配器实现(第二个是OK)不满足Allocator
部分的rebind
要求},见17.6.3.5表28:
+------------------+-------------+-------------------------------------+ | Expression | Return type | Assertion/note pre-/post- condition | +------------------+-------------+-------------------------------------+ | typename | Y | For all U (including T), | | X::template | | Y::template rebind<T>::other is X. | | rebind<U>::other | | | +------------------+-------------+-------------------------------------+
对于第二个问题:GCC具有std :: list的旧的,前C ++ 11实现,它将仅在GCC 5.0中修复,因为此更改会破坏ABI(有关详细信息,请参阅Should std::list::size have constant complexity in C++11?)
但是,引用的标准要求,该容器必须为完全 construct
调用allocator_type
函数而不是某些重新绑定类型,似乎是标准缺陷({{3} })。 std::set
,multiset
,map
和multimap
的Libstdc ++实现依赖于此事实,并使用rebinded allocator for construct(http://cplusplus.github.io/LWG/lwg-active.html#2218)。