Sil*_*ler 11 c++ vector allocator c++11
假设您有一个Container,它在内部使用其他标准容器来形成更复杂的数据结构.值得庆幸的是,标准容器已经设计用于执行所有必要的工作以确保分配器被复制/分配等.
所以,通常如果我们有一些Container c,并且在内部它有一个std::vector<int>,我们可以编写一个复制赋值运算符,它只是说:
Container& operator = (const Container& c) { m_vec = c.m_vec; return *this; }
Run Code Online (Sandbox Code Playgroud)
实际上我们甚至不必编写它(因为它只是默认的复制赋值运算符所做的),但是我们只是说在这种情况下,默认运算符不会执行一些额外的必需逻辑:
Container& operator = (const Container& c)
{
/* some other stuff... */
m_vec = c.m_vec;
return *this;
}
Run Code Online (Sandbox Code Playgroud)
因此,在这种情况下没有问题,因为向量赋值运算符为我们完成了所有工作,以确保分配器正确地复制或不复制.
但是......如果我们有一个我们不能简单地复制分配的矢量怎么办?假设它是指向其他内部结构的指针的向量.
假设我们有一个包含指针的内部向量: std::vector<node*, Alloc>
所以,通常在我们的复制赋值运算符中,我们必须说:
Container& operator = (const Container& other)
{
vector<node*, Alloc>::allocator_type alloc = m_vec.get_allocator();
for (auto it = m_vec.begin(); it != m_vec.end(); ++it) alloc.deallocate(*it);
m_vec.clear();
for (auto it = other.m_vec.begin(); it != other.m_vec.end(); ++it)
{
node* n = alloc.allocate(1); // this is wrong, we might need to use other.get_allocator() here!
alloc.construct(n, *(*it));
m_vec.push_back(n);
}
return *this;
}
Run Code Online (Sandbox Code Playgroud)
因此,在上面的示例中,我们需要手动释放所有node对象m_vec,然后从RHS容器构造新的节点对象.(请注意,我正在使用vector在内部使用的分配器对象,以便分配节点对象.)
但是如果我们想在这里和AllocatorAware符合标准,我们需要检查allocator_traits<std::vector<node*, Alloc>::allocator_type>设置是否propagate_on_container_copy_assign为true.如果是这样,我们需要使用另一个容器的分配器来构造复制的节点.
但是......我们的容器类型Container不使用它自己的分配器.它只是使用内部std::vector...所以如何std::vector在必要时告诉我们的内部实例使用复制的分配器?向量没有类似"use_allocator"或"set_allocator"成员函数的东西.
所以,我想出的唯一一件事就是:
if (std::allocator_traits<Alloc>::propagate_on_container_copy_assignment::value)
{
m_vec = std::vector<node*, Alloc>(other.get_allocator());
}
Run Code Online (Sandbox Code Playgroud)
...然后我们可以用返回值构造我们的节点 m_vec.get_allocator();
这是一个有效的习惯用法,用于创建一个不支持自己的分配器的分配器感知容器,而是推迟到内部标准容器?
swap在此示例中,用于实现副本分配的一个问题是,如果propagate_on_assignment == true_type和propagate_on_container_swap == false_type,则分配器不会从传播other到*this,因为swap拒绝这样做。
这种方法的第二个问题是,如果propagate_on_assignmentand和propagate_on_container_swap == true_typebut other.m_vec.get_allocator() != m_vec.get_allocator()都传播了分配器,但是在的时候得到了未定义的行为swap。
为了做到这一点,您确实需要operator=从头开始设计主体。对于本练习,我假设这Container看起来像这样:
template <class T, class Alloc>
struct Container
{
using value_type = T;
static_assert(std::is_same<typename Alloc::value_type, value_type>{}, "");
using allocator_type = Alloc;
struct node {};
using NodePtr = typename std::pointer_traits<
typename std::allocator_traits<allocator_type>::pointer>::template
rebind<node>;
using NodePtrAlloc = typename std::allocator_traits<allocator_type>::template
rebind_alloc<NodePtr>;
std::vector<NodePtr, NodePtrAlloc> m_vec;
// ...
Run Code Online (Sandbox Code Playgroud)
即Container在T和上进行模板化Alloc,并且该实现允许Alloc使用“花式指针”(即node*实际上是类类型)的可能性。
在这种情况下,Container复制分配操作符可能如下所示:
Container&
operator = (const Container& other)
{
if (this != &other)
{
using NodeAlloc = typename std::allocator_traits<NodePtrAlloc>::template
rebind_alloc<node>;
using NodeTraits = std::allocator_traits<NodeAlloc>;
NodeAlloc alloc = m_vec.get_allocator();
for (auto node_ptr : m_vec)
{
NodeTraits::destroy(alloc, std::addressof(*node_ptr));
NodeTraits::deallocate(alloc, node_ptr, 1);
}
if (typename NodeTraits::propagate_on_container_copy_assignment{})
m_vec = other.m_vec;
m_vec.clear();
m_vec.reserve(other.m_vec.size());
NodeAlloc alloc2 = m_vec.get_allocator();
for (auto node_ptr : other.m_vec)
{
using deleter = allocator_deleter<NodeAlloc>;
deleter hold{alloc2, 1};
std::unique_ptr<node, deleter&> n{NodeTraits::allocate(alloc2, 1),
hold};
NodeTraits::construct(alloc2, std::addressof(*n), *node_ptr);
hold.constructed = true;
m_vec.push_back(n.get());
n.release();
}
}
return *this;
}
Run Code Online (Sandbox Code Playgroud)
说明:
为了使用兼容的分配器分配和释放内存,我们需要使用std::allocator_traits创建一个“ allocator<node>”。NodeAlloc在上面的示例中将其命名。为上述分配器形成特征也很方便NodeTraits。
第一任务是一个变换的LHS分配器(从转换的拷贝allocator<node*>到allocator<node>),并使用该分配器来既破坏和解除分配LHS节点。 std::addressof需要将可能的“奇特指针”转换为node*对的调用中的实际指针destroy。
接下来,这有点微妙,我们需要传播m_vec.get_allocator()到m_vec,但前提propagate_on_container_copy_assignment是必须为true。的副本分配运算符vector是执行此操作的最佳方法。这不必要地复制了一些NodePtrs,但是我仍然相信这是使该分配器传播的最佳方法。如果propagate_on_container_copy_assignment为false,我们也可以进行向量分配,从而避免了if语句。如果propagate_on_container_copy_assignment为false,分配将不会传播分配器,但是NodePtr当我们真正需要的只是一个无操作时,我们仍然可以分配一些。
如果propagate_on_container_copy_assignment为true,并且两个分配器不相等,则vector副本分配运算符将在分配分配器之前为我们正确处理lhs资源的转储。这是一个易于忽略的复杂情况,因此最好由vector副本分配运算符决定。
如果propagate_on_container_copy_assignment为false,则意味着我们不必担心分配器不相等的情况。我们不会交换任何资源。
无论如何,在执行clear()完此操作后,我们应该执行lhs。此操作不会转储capacity(),因此不会浪费。在这一点上,我们有一个大小为零的lhs,带有正确的分配器,甚至可能还有一些非零大小的lh capacity()。
作为一种优化,我们可以reserve用other.size(),以防LHS能力是不够的。该行对于正确性不是必需的。这是一个纯粹的优化。
以防万一m_vec.get_allocator()现在可能返回一个新的分配器,我们继续获取它的新副本,alloc2上面命名。
现在alloc2,我们可以用来分配,构造和存储从rhs复制构造的新节点。
为了安全起见,在构造指针时应使用RAII设备来保存分配的指针,并将其推入向量中。任何一种构造都可以抛出,而push_back()。RAII设备必须知道在异常情况下是只需要释放还是需要销毁和释放。RAII设备还需要具有“花式指针”意识。事实证明,std::unique_ptr结合使用自定义删除器来构建所有这些对象非常容易:
template <class Alloc>
class allocator_deleter
{
using traits = std::allocator_traits<Alloc>;
public:
using pointer = typename traits::pointer;
using size_type = typename traits::size_type;
private:
Alloc& alloc_;
size_type s_;
public:
bool constructed = false;
allocator_deleter(Alloc& a, size_type s) noexcept
: alloc_(a)
, s_(s)
{}
void
operator()(pointer p) noexcept
{
if (constructed)
traits::destroy(alloc_, std::addressof(*p));
traits::deallocate(alloc_, p, s_);
}
};
Run Code Online (Sandbox Code Playgroud)
请注意std::allocator_traits对分配器的所有访问的一致用法。这允许std::allocator_traits提供默认值,以便Alloc不需要的作者提供它们。例如std::allocator_traits可以提供默认的实现construct,destroy和propagate_on_container_copy_assignment。
还要注意避免一致的假设NodePtr是node*。
利用现有功能似乎是合理的。就我个人而言,我会更进一步,实际上利用现有的实现来完成复制。一般来说,合适的复制和交换习惯用法似乎是实现复制分配的最简单方法:
Container& Container::operator= (Container const& other) {
Container(other,
std::allocator_traits<Alloc>::propagate_on_assignment
? other.get_allocator()
: this->get_allocator()).swap(*this);
return *this;
}
Run Code Online (Sandbox Code Playgroud)
不过,这种方法做出了一些假设:
复制构造函数以[可选]获取传递的分配器的形式实现:
Container::Container(Container const& other, Alloc allcoator = Alloc()));
Run Code Online (Sandbox Code Playgroud)它假设swap()适当地交换分配器。
值得指出的是,这种方法的优点是相当简单并提供强大的异常保证,但它使用新分配的内存。如果LHS对象的存储器被重用,则可能会导致更好的性能,例如,因为所使用的存储器已经相当接近处理器。也就是说,对于初始实现,我将使用复制和交换实现(使用如上所述的扩展复制构造函数),并在分析表明需要时将其替换为更复杂的实现。
| 归档时间: |
|
| 查看次数: |
427 次 |
| 最近记录: |