自定义指针类型和容器/分配器typedef

时间:2015-02-14 22:54:06

标签: c++ c++11

C ++标准容器和分配器为容器使用的指针类型提供typedef,即:

typename std::vector<T>::pointer
typename std::vector<T>::const_pointer

用于创建typedef的实际指针类型是通过std::allocator_traits

确定的
typedef typename std::allocator_traits<Allocator>::pointer pointer;

由于每个容器也有value_type typedef,因此pointer typedef的目的可能是某种奇怪的情况,其中使用的指针类型是 other 而不是{{ 1}}。我从来没有亲自看到类似的用例,但我想标准委员会希望提供使用容器的自定义指针类型的可能性。

问题在于,这似乎与value_type*中为函数提供的定义不一致。具体来说,在std::allocator_traits中我们有std::allocator_traits函数,其定义为:

construct

...只需拨打template <class T, class... Args> static void construct(Alloc& a, T* p, Args&&... args);

但请注意,此函数不对自定义指针类型进行任何规定。参数a.construct(p, std::forward<Args>(args)...)是一个普通的本机指针。

那么,为什么这个函数的定义不像是:

p

如果没有这个,使用template <class... Args> static void construct(Alloc& a, typename Alloc::pointer p, Args&&... args); 的容器如果与定义了一些自定义指针类型的Allocator一起使用就会失败。

那么,这里发生了什么?或者我是否误解了首先使用std::allocator_traits<Alloc>::construct typedef的目的?

1 个答案:

答案 0 :(得分:7)

这种二分法是有目的的,并不存在问题。 construct成员函数通常如下所示实现:

template <class U, class ...Args>
void
construct(U* p, Args&& ...args)
{
    ::new(static_cast<void*>(p)) U(std::forward<Args>(args)...);
}

即。它转发到展示位置new,后者又有此签名:

void* operator new  (std::size_t size, void* ptr) noexcept;

所以最终你需要一个“真正的”指针来调用新的位置。并且为了传达需要构造的对象的类型,在指针类型中传递该信息是有意义的(例如U*)。

对于对称性,destroy也是根据实际指针制定的,通常如此实现:

template <class U>
void
destroy(U* p)
{
    p->~U();
}

“花式指针”的主要用例是将对象放入共享内存中。名为offset_ptr的类通常用于此目的,可以创建分配器来分配和释放offset_ptr引用的内存。因此,allocator_traitsallocator allocatedeallocate功能的流量均为pointer而不是value_type*

所以问题出现了:如果你有一个pointer,需要一个T*,你会怎么做?

T*

创建pointer p有两种我知道的技巧

1. std::addressof(*p);

取消引用pointer p时,必须根据标准生成左值。但是,能够放宽此要求会很好(例如,考虑pointer返回代理引用,例如vector<bool>::reference)。 std::addressof被指定为将T*返回到任何左值:

template <class T> T* addressof(T& r) noexcept;

2. to_raw_pointer(p); // where:

template <class T>
inline
T*
to_raw_pointer(T* p) noexcept
{
    return p;
}

template <class Pointer>
inline
typename std::pointer_traits<Pointer>::element_type*
to_raw_pointer(Pointer p) noexcept
{
    return ::to_raw_pointer(p.operator->());
}

这会调用pointer的{​​{1}},它会直接返回operator->()或转发给直接或间接返回T*的内容。所有T*类型都应该支持pointer,即使它引用了operator->()。这种技术的缺点是,除非bool不可用,否则当前operator->()不需要被调用。应该在标准中解除这一限制。

在C ++ 14中,第二个重载的返回类型(实际上两个都是重载)可以方便地替换为pointer


如果你有一个auto并希望构建一个T*,那你就不走运了。没有可移植的方式来转换这个方向。


另请注意tangentially related LWG issue关于pointer成员函数的返回类型。它已在vector::data()value_type*之间退回,并且当前(并且有目的地)pointer