【发布时间】:2014-11-10 20:24:15
【问题描述】:
在 23.2.1p3 C++11 标准中,我们可以阅读:
对于声明
allocator_type的受本子条款影响的组件,存储在这些组件中的对象应使用allocator_traits<allocator_type>::construct函数构造并使用allocator_traits<allocator_type>::destroy函数(20.6.8.2) 销毁。这些函数只为容器的元素类型调用,而不是容器使用的内部类型。 [ 注意:这意味着,例如,基于节点的容器可能需要构造包含对齐缓冲区的节点并调用construct将元素放入缓冲区。 —尾注 ]
allocator_traits<allocator_type>::construct 只是调用传递的分配器的construct 方法,如果分配器定义了一个。我尝试使用它并创建分配器,它使用列表初始化进行构造,因此我可以利用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 个问题是违反标准还是我遗漏了什么?
【问题讨论】:
-
第一个问题显然不是违规——嵌套重新绑定结构的全部目的是允许容器在必要时分配“包装器”。除了
vector之外,几乎每个容器都必须这样做。我在标准中没有看到任何地方禁止标准库实现对第二种情况使用直接初始化,但是我对此的第一个参考结果证明是不正确的,所以我现在删除了我的答案。 -
对于第一个问题:我认为,在本段标准中,容器应使用
allocator_type::construct进行对象构造,其中每个容器的allocator_type定义为typedef Allocator allocator_type,其中Allocator是容器的模板参数。所以使用rebind获取内存是可以的,但是对于我们的对象构造就不行了。我错了吗? -
这可能是 libstdc++ 中的一个错误,是的。你引用的那一点似乎表明了这一点。
-
旁白:C++ 的设计并不是围绕要求对所有内容进行继承。继承是一种非常紧密的耦合关系,通常,要正确使用它,继承类需要在设计时考虑到它,并且继承类需要对父类有很好的理解。从
std::allocator继承对于实现分配器不是必需的,并且该类不是专门为它设计的。这是您可能不应该使用继承的一种情况。