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

时间:2014-12-25 11:59:26

标签: c++ pointers c++11 memory-management standard-library

我尝试创建一个使用智能指针的自定义内存分配器。我没有发布代码,因为它太大而且没有添加太多信息。然后我用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]));
    ....
    }

错误消息有点令人困惑。真正的问题是this->_Mylast类型为my_allocator< int >::pointer,这是一个智能指针,构造方法需要int*

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

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

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

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

  

[17.6.3.5-5]分配器类型X应满足CopyConstructible(17.6.3.1)的要求。 X::pointerX::const_pointerX::void_pointerX::const_void_pointer类型应满足NullablePointer(17.6.3.3)的要求。对这些类型的构造函数,比较运算符,复制操作,移动操作或交换操作不应通过异常退出。 X::pointerX::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);
};

1 个答案:

答案 0 :(得分:13)

为了解决这个问题,我创建了一个to_raw_pointer函数,它恰好适用于任何&#34;花式指针&#34;它实现了operator->()。您可以在libc++ implementation

中找到它

这是:

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->());
}

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

this->_Getal().construct(__to_raw_pointer(this->_Mylast),
            _STD forward<value_type>(this->_Myfirst[_Idx]));
指定

construct采用实际指针,而不是花哨的指针。并且没有从指向真实指针的花式指针指定的隐式转换。容器必须使用to_raw_pointeraddressof等内容。

容器还需要通过construct调用allocator_traits,而不是直接在存储的分配器上调用它,如图所示。这是为了让construct成为默认&#34;由allocator_traits提出,而不是要求分配者实施construct

目前,operator*()operator->()通常都要求花式指针在调用该运算符之前为非空。但是我预计将来operator->()将放宽此要求。

<强>更新

当我写上述内容时,我有点匆忙。现在我有时间,我将在allocator::pointer类型中包含完整的要求。然而,在重新阅读这个问题时,我发现Maxym已经在问题中做得很好,所以我不会在这里重复这些问题。

std中的一件事,但并不完全明显,是四种指针类型之间的隐式和显式转换:pointerconst_pointervoid_pointer和{ {1}}:

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 | +--------------------------------------+ 转换为const,从非const隐式转换为void,您可以显式void转换为非 - void。但是voidconst_cast的容器无法const(抛弃allocator::const_pointer - ness)。一旦容器进入allocator::const_void_pointer,它就永远无法返回。