具有自定义指针类型的内存分配器

Max*_*xym 12 c++ pointers memory-management standard-library c++11

我试图创建一个使用智能指针的自定义内存分配器.我没有发布代码,因为它太大而且没有添加太多信息.然后我用一个测试它std::vector.它在Xcode上运行得非常好.但是当我尝试在Visual Studio 12(2013)中构建相同的代码时,构建失败并出现以下错误:

... vector(873):错误C2660:' std::_Wrap_alloc< my_allocator< int > >::construct':函数不带2个参数

问题出在push_back方法中:

void push_back(value_type&& _Val)
    {
    ....
        this->_Getal().construct(this->_Mylast,
            _STD forward<value_type>(this->_Myfirst[_Idx]));
    ....
    }
Run Code Online (Sandbox Code Playgroud)

错误消息有点令人困惑.真正的问题是this->_Mylast类型my_allocator< int >::pointer,它是一个智能指针,而构造方法是期望的int*.

所以,问题很简单:自定义内存分配器中使用的指针类型有哪些要求?应该X::pointer可以转换为原始指针?如果是的话,它会让它们变得毫无用处.

实际上我希望这行代码看起来像:

this->_Getal().construct(addressof(*(this->_Mylast)),
            _STD forward<value_type>(this->_Myfirst[_Idx]));
Run Code Online (Sandbox Code Playgroud)

让我们尝试在C++标准中找到答案,其中说:

[17.6.3.5-5]分配器类型X应满足CopyConstructible(17.6.3.1)的要求.的X::pointer,X::const_pointer,X::void_pointer,和X::const_void_pointer类型应满足NullablePointer(17.6.3.3)的要求.对这些类型的构造函数,比较运算符,复制操作,移动操作或交换操作不应通过异常退出.X::pointer并且X::const_pointer还应满足随机访问迭代器的要求(24.2)

如果我们看看NullablePointer reqs,他们会添加一些其他要求:

[17.6.3.3] NullablePointer类型是一种类似指针的类型,支持空值.如果符合以下条件,则类型P满足NullablePointer的要求:
(1.1) - P满足EqualityComparable,DefaultConstructible,CopyConstructible,CopyAssignable和Destructible的要求......

如果我检查随机访问迭代器的要求,我也没有找到任何明确提到它的转换为原始指针.但是在很少的地方使用了方法addressof(例如24.2.1-5).

此外,它并不是Microsoft std::vector实现中唯一X::pointer与假定原始指针相等的地方.我想知道,我错过了什么?

编辑:我将在这里添加一段my_allocator deffinition:

class my_allocator
{
public:

typedef std::size_t          size_type;
typedef std::ptrdiff_t       difference_type;
typedef my_ptr<T>            pointer;
typedef my_ptr<const T>      const_pointer;
typedef T&                   reference;
typedef const T&             const_reference;
typedef T                    value_type;
typedef my_ptr<void>         void_pointer;
typedef my_ptr<const void>   const_void_pointer;

<constructors>

pointer allocate(size_type n, const_void_pointer = nullptr);
void deallocate(const pointer& ptr, size_type elements_num);
};
Run Code Online (Sandbox Code Playgroud)

How*_*ant 13

为了解决这个问题,我创建了一个to_raw_pointer函数,它恰好适用于任何实现的"花式指针" operator->().您可以在libc ++实现中找到它.

这里是:

template <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY
_Tp*
__to_raw_pointer(_Tp* __p) _NOEXCEPT
{
    return __p;
}

template <class _Pointer>
inline _LIBCPP_INLINE_VISIBILITY
typename pointer_traits<_Pointer>::element_type*
__to_raw_pointer(_Pointer __p) _NOEXCEPT
{
    return _VSTD::__to_raw_pointer(__p.operator->());
}
Run Code Online (Sandbox Code Playgroud)

它通过operator->()以非常规方式调用它来工作.此运算符必须调用另一个operator->(),或返回一个真正的指针.实指针的重载会使用标识函数打破递归.所以这将被用作:

this->_Getal().construct(__to_raw_pointer(this->_Mylast),
            _STD forward<value_type>(this->_Myfirst[_Idx]));
Run Code Online (Sandbox Code Playgroud)

construct被指定采用真正的指针,而不是花哨的指针.并且没有从指向真实指针的花式指针指定的隐式转换.容器必须使用诸如to_raw_pointer或之类的东西addressof.

容器也需要调用constructvia allocator_traits,而不是直接在存储的分配器上调用它,如图所示.这是为了允许construct"默认" allocator_traits,而不是要求分配器实现construct.

目前operator*(),operator->()在调用该运算符之前,它们通常都要求花式指针为非null.但是我预计这个要求将来会放宽operator->().

更新

当我写上面的内容时,我有点匆忙.现在我有时间,我将包括对allocator::pointer类型的完整要求.然而,在重新阅读这个问题时,我发现Maxym已经在问题中做得很好,所以我在此不再赘述.

有一件事是性病,但不完全是显而易见的,是四个指针类型之间的隐性和显性的转换:pointer,const_pointer,void_pointer,和const_void_pointer:

implicit allocator pointer conversions:
+--------------------------------------+
| pointer      -->  const_pointer      |
|    |    \               |            |
|    |      ---------     |            |
|   \|/             _\|  \|/           |
| void_pointer -->  const_void_pointer |
+--------------------------------------+


explicit allocator pointer conversions:
+--------------------------------------+
| pointer           const_pointer      |
|   /|\                  /|\           |
|    |                    |            |
|    |                    |            |
| void_pointer      const_void_pointer |
+--------------------------------------+
Run Code Online (Sandbox Code Playgroud)

也就是说,你可以隐式地从非转换constconst,并从非voidvoid了,你可以明确地从转换void到非void.但是没有办法让一个容器const_cast(抛弃const)来自一个allocator::const_pointer或者allocator::const_void_pointer.一旦容器运行const,它就永远不会回来.

  • 很酷,但它实际上与 addressof(*(this-&gt;_Mylast)) 相同,因为无论如何标准都要求指针间接。我试图弄清楚是我做错了什么(因为我误解了 C++ 标准)还是 VS STL 实现中的错误。 (2认同)