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

Sil*_*ler 9 c++ c++11

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

typename std::vector<T>::pointer
typename std::vector<T>::const_pointer
Run Code Online (Sandbox Code Playgroud)

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

typedef typename std::allocator_traits<Allocator>::pointer pointer;
Run Code Online (Sandbox Code Playgroud)

由于每个容器也有一个value_type类型定义,想必目的pointer的typedef是一些奇怪的情况下使用的指针类型的东西其他value_type*.我从来没有亲自看到类似的用例,但我认为标准委员会希望提供使用容器的自定义指针类型的可能性.

问题是,这似乎与为其中的函数提供的定义不一致std::allocator_traits.具体来说,std::allocator_traits我们有construct函数,定义为:

template <class T, class... Args>
static void construct(Alloc& a, T* p, Args&&... args);
Run Code Online (Sandbox Code Playgroud)

...只是打电话 a.construct(p, std::forward<Args>(args)...)

但请注意,此函数不对自定义指针类型进行任何规定.该参数p是一个普通的本机指针.

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

template <class... Args>
static void construct(Alloc& a, typename Alloc::pointer p, Args&&... args);
Run Code Online (Sandbox Code Playgroud)

似乎没有这个,std::allocator_traits<Alloc>::construct如果与定义了一些自定义指针类型的Allocator一起使用,那么使用的容器将会失败.

那么,这里发生了什么?或者我是否误解pointer了首先使用typedef 的目的?

How*_*ant 7

这种二分法是有目的的,并不存在问题.所述construct成员函数通常被实现这样的:

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

即它转发到展示位置new,而展示位置又具有此签名:

void* operator new  (std::size_t size, void* ptr) noexcept;
Run Code Online (Sandbox Code Playgroud)

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

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

template <class U>
void
destroy(U* p)
{
    p->~U();
}
Run Code Online (Sandbox Code Playgroud)

"花式指针"的主要用例是将对象放入共享内存中.调用的类offset_ptr通常用于此目的,并且可以创建分配器来分配和释放由其引用的内存offset_ptr.因此,allocator_traits而且用来代替allocator allocatedeallocate交换功能.pointervalue_type*

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

我知道有两种技术用于创建T*apointer p

1. std::addressof(*p);

当您取消引用a时pointer p,它必须根据标准产生左值.然而,能够放松这个要求会很好(例如,考虑pointer返回代理引用,例如vector<bool>::reference).std::addressof被指定为返回T*任何左值:

template <class T> T* addressof(T& r) noexcept;
Run Code Online (Sandbox Code Playgroud)

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->());
}
Run Code Online (Sandbox Code Playgroud)

这会调用一个pointer's operator->(),它将直接返回T*或转发给直接或间接返回a的东西T*.所有pointer类型都应该支持operator->(),即使它引用了一个bool.这种技术的缺点是,operator->()除非pointer是不可取的,否则当前不需要调用.应该在标准中解除这一限制.

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


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


还应注意这个切向相关LWG问题有关vector::data()成员函数的返回类型.它已经在之间和之后反弹value_type*,pointer目前(并且有目的地)value_type*.