【问题标题】:Custom pointer types and container/allocator typedefs自定义指针类型和容器/分配器类型定义
【发布时间】:2015-02-14 22:54:06
【问题描述】:

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 的用途大概是用于某些奇怪的情况,即使用的指针类型其他而不是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&lt;Args&gt;(args)...)

但请注意,此函数没有为自定义指针类型做任何规定。参数p 是一个普通的原生指针。

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

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

如果没有这个,使用 std::allocator_traits&lt;Alloc&gt;::construct 的容器如果与定义一些自定义指针类型的分配器一起使用,将会失败。

那么,这里发生了什么?还是我首先误解了pointer typedefs 的目的?

【问题讨论】:

  • construct 是/必须固定到元素 type。同样与destroyallocatedeallocate 不是。 allocatedeallocate 都指您正在检查的 pointer 类型。 这些函数都不处理实际的对象constructiondestruction,因此没有保证固定到类型T。但是,constructdestroy
  • @dyp ::std::addressof(*p),而是;)
  • pointer != value_type* 的整个想法似乎已经(有点)在 C++11 中引入。据我了解,在 C++03 中,容器实现者可以假设 pointer == value_type*。我目前正在搜索有关为什么更改它的建议。它似乎与分配器概念文件 (N2654) 和潜在的作用域分配器模型 (N2554) 有关。
  • 重读论文后,N2654 - Allocator Concepts (revision 1) 似乎解除了pointer == value_type* 的限制。这可能与使用智能/花式指针有关,例如用于共享内存分配器。

标签: c++ c++11


【解决方案1】:

这种二分法是有目的的,不会造成问题。 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;

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

为了对称,destroy 也是根据实际指针来表述的,通常这样实现:

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

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

那么问题来了:如果你有一个pointer,并且需要一个T*,你会怎么做?

我知道有两种技术可以从pointer p 创建T*

1.std::addressof(*p);

当您取消引用 pointer p 时,它必须根据标准产生一个左值。然而,能够放宽这个要求会很好(例如,考虑一个 pointer 返回一个代理引用,例如 vector&lt;bool&gt;::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->());
}

这会调用pointeroperator-&gt;(),这将直接返回T* 或转发到将直接或间接返回T* 的内容。所有pointer 类型应该支持operator-&gt;(),即使它引用bool。这种技术的一个缺点是当前operator-&gt;() 需要不被调用,除非pointer 是可取消引用的。应该在标准中取消该限制。

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


如果你有一个T* 并希望构造一个pointer,那么你就不幸了。没有可移植的方式可以朝这个方向进行转换。


还要注意tangentially related LWG issue 关于vector::data() 成员函数的返回类型。它在value_type*pointer 和回来之间反弹,目前(并且有目的地)是value_type*

【讨论】:

  • 我的意思是在标准中应该放宽这个要求。谢谢,我会澄清我粗心的语言。
  • “所以最终你需要一个“真正的”指针来调用placement new。” 但是,allocator 应该知道如何获得@ 987654368@ 来自pointer.. 那么为什么分配器不负责进行转换/提取呢?
  • 这将是对分配器 API 的不必要添加。事实上,C++98/03 分配器实际上在 address 成员函数中有这样一个 API,而这个要求被简单地放弃了,取而代之的是更有用的独立实用程序 std::addressof()
  • 在我对 OP 的 cmets 中,我推测了指针算法。据我所知,pointer 不需要支持指针运算,因此除了数组的第一个位置之外,不可能在任何位置构造元素 - 正如你所说,不可能从 T* 转换回来到pointer。这可能是另一个原因吗?
  • @dyp:不,我不这么认为。 [allocator.requirements]/p5 部分表示:X::pointerX::const_pointer 还应满足随机访问迭代器 (24.2) 的要求。这需要指针算法。
猜你喜欢
  • 2015-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-03
  • 1970-01-01
  • 1970-01-01
  • 2017-10-01
  • 2013-09-09
相关资源
最近更新 更多