我发现自己处于这样一种情况,我希望有一个类似于unique_ptrs release()for的情况std::vector<>。例如:
std::vector<int> v(SOME_SIZE);
//.. performing operations on v
int* data = v.release(); // v.size() is now 0 and the ownership of the internal array is released
functionUsingAndInternallyDeletingRowPointer(data);
Run Code Online (Sandbox Code Playgroud)
有什么特殊原因不提供这种可能性吗?std::vector这会对内部实施施加一些限制吗?
或者有一种方法可以实现这一点,而我却尴尬地错过了?
我有 avector<vector<int>>并且希望从a 中获取整个内存(即,外部和内部向量的)memory_resource。这是一个精简的示例,首先是无聊的部分:
#include <boost/container/pmr/memory_resource.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <boost/container/pmr/polymorphic_allocator.hpp>
#include <iostream>
#include <string>
#include <vector>
// Sample memory resource that prints debug information
class MemoryResource : public boost::container::pmr::memory_resource {
void* do_allocate(std::size_t bytes, std::size_t alignment) {
std::cout << "Allocate " << bytes << " bytes" << std::endl;
return malloc(bytes);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) { free(p); }
bool do_is_equal(const memory_resource& other) const noexcept { return true; }
};
Run Code Online (Sandbox Code Playgroud)
这是我感兴趣的部分:
template <typename T>
using Alloc …Run Code Online (Sandbox Code Playgroud) 标准 C++ 库中有许多类可能会分配内存但不接受分配器。其中一些这样做是因为在类型擦除的上下文中分配内存是不可能的。
一个例子是 std::any 有一个构造函数,该构造函数在其设计的某个时刻接受了一个 Allocator 参数,但由于似乎无法实现而被丢弃。我确实对这种情况考虑了一段时间,我想知道阻止其实施的确切问题是什么。不能满足标准的哪些要求?
假设我们从any. 分配内存很简单:
struct any {
struct type_interface;
template <typename T>
struct type_impl;
type_interface* value;
any(T&& value, const Allocator& allocator = Allocator()) {
using actual_allocator_t
= std::allocator_traits<Allocator>::rebind_alloc<type_impl<T>>;
actual_allocator_t actual_allocator;
// do allocate
// do construct
// assign obtained pointer
}
};
Run Code Online (Sandbox Code Playgroud)
问题显然是我们失去了最初分配对象的分配器type_impl<T>。一个技巧可能是创建一个方法来声明一个静态变量来存储该分配器。
template <typename Allocator>
auto& get_allocator(const Allocator& allocator) {
using actual_allocator_t = std::allocator_traits<Allocator>::rebind_alloc<type_impl<T>>;
// static variable: initialized just on the …Run Code Online (Sandbox Code Playgroud) c++ memory-management type-erasure allocator c++-standard-library
标准分配器可以选择提示作为默认参数https://en.cppreference.com/w/cpp/memory/allocator/allocate
T* allocate( std::size_t n, const void * hint);
Run Code Online (Sandbox Code Playgroud)
暂且不说这在 C++20 中已被正式弃用(这显然并不意味着分配器不能具有提示参数):
您知道hint现有代码或理论代码中标准或非标准分配器实现的良好用途吗?或者说它只是一个普通的历史遗迹?
我试图了解当前的提示是否可以帮助您在拥有多个设备(例如 GPU)时进行分配。
注 1:
我不是问如何在 cpu 或 gpu 中分配内存,我试图查看hint在内部使用此参数的良好或经过验证的代码,大概是效率和特定类型的内存。即使它是一些奇异的系统。
注2: 我不是问如何/如果/什么作为提示参数传递(即“仅传递容器的当前指针”),就像链接的问题中一样。我是从实现自定义分配器的人的角度来问的。
cppreference 的 std::allocator 示例包含以下代码(为简单起见缩短):
// default allocator for ints
std::allocator<int> alloc1;
using traits_t1 = std::allocator_traits<decltype(alloc1)>; // The matching trait
p1 = traits_t1::allocate(alloc1, 1);
traits_t1::construct(alloc1, p1, 7); // construct the int
std::cout << *p1 << '\n';
Run Code Online (Sandbox Code Playgroud)
就分配器而言,相当简单。然而,标准保证中的哪些措辞p1实际上指向新对象呢?
根据std::allocate和[allocator.members]上的 cppreference 文档,默认分配器的函数allocate()
在存储中创建 T[n] 类型的数组并开始其生存期,但不开始其任何元素的生存期。
并返回
[指针] 指向 n 个 T 类型对象的数组的第一个元素,该数组的元素尚未构造。
Afaik,数组创建措辞已添加到标准中,以便指针上的指针算术有效。无论如何,这意味着返回的指针指向 的第一个元素,T[]并且该第一个元素的生命周期尚未开始。
construct()然后在此位置创建一个对象,但是,它不会返回指向该对象的指针。我们拥有的唯一指针仍然是返回的指针allocate。
通常,当一个对象被放置在过期对象的位置时,它可以在[basic.life]p8中规定的条件下“透明地替换”旧对象:(强调我的)
如果在一个对象的生命周期结束之后,在该对象所占用的存储被重用或释放之前,在原对象所占用的存储位置上创建了一个新对象,一个指向原对象的指针[... ] 将自动引用新对象,并且一旦新对象的生命周期开始[...]
该数组元素的生命周期从未开始,因此它不可能结束,因此这不适用。那么如何才能保证对新构造的对象的访问是明确定义的呢?每次通话后都std::launder应该使用吗construct …
当将类型传递给要作为分配器的类时,C++ 03标准库使用简单的模板类型参数.这是可能的,因为模板在C++中的工作方式.但是,它并不是非常简单,您可能不知道类型定义应该是什么样子 - 特别是在非标准类型的情况下.
我认为使用适配器类instread可能是个好主意.我已经创建了一个示例来向您展示我的意思:
#ifndef HPP_ALLOCATOR_ADAPTOR_INCLUDED
#define HPP_ALLOCATOR_ADAPTOR_INCLUDED
#include <memory>
template<typename T>
struct allocator_traits;
template<typename T, class allocator_type = std::allocator<T>>
class allocator_adaptor;
template<>
struct allocator_traits<void>
{
typedef std::allocator<void>::const_pointer const_pointer;
typedef std::allocator<void>::pointer pointer;
typedef std::allocator<void>::value_type value_type;
};
template<typename T>
struct allocator_traits
{
typedef typename std::allocator<T>::const_pointer const_pointer;
typedef typename std::allocator<T>::const_reference const_reference;
typedef typename std::allocator<T>::difference_type difference_type;
typedef typename std::allocator<T>::pointer pointer;
typedef typename std::allocator<T>::reference reference;
typedef typename std::allocator<T>::size_type size_type;
typedef typename std::allocator<T>::value_type value_type;
};
template<class allocator_type>
class allocator_adaptor<void, allocator_type>
: public allocator_traits<void>
{ …Run Code Online (Sandbox Code Playgroud) 使用时std::allocator,该deallocate函数需要一个pointer参数和一个size_type参数(std::allocator<>::deallocate(std::allocator<>::pointer p, std::allocator<>::size_type).但是,size_type不使用,也不是可选的.那么为什么会出现?它真的让我困惑,因为它应该是可选的,甚至不是那里,因为它没有在函数中使用.
编辑:MSVC的allocator实现deallocate
void deallocate(pointer _Ptr, size_type)
{ // deallocate object at _Ptr, ignore size
::operator delete(_Ptr);
}
Run Code Online (Sandbox Code Playgroud) 我最近一直试图理解c ++分配器是如何工作的,而且我一直在寻找STL库使用的红黑树的实现,std::set或者std::map有些东西是我无法得到的.到处走走.
首先要做的是将分配器从容器必须存储_Val的类型 - 转换为树使用的节点类型_Rb_tree_node<_Val>- 使用重新绑定模板:
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Rb_tree_node<_Val> >::other _Node_allocator;
typedef __gnu_cxx::__alloc_traits<_Node_allocator> _Alloc_traits;
Run Code Online (Sandbox Code Playgroud)
我可以解决这个问题.
现在,当插入一个元素并且需要创建一个新节点时,它的作用就是这个
_Node_type __node = _Alloc_traits::allocate(_M_get_Node_allocator(), 1);
Run Code Online (Sandbox Code Playgroud)
我假设为单个节点分配空间.但是它就是这样做的
::new(__node) _Rb_tree_node<_Val>;
Run Code Online (Sandbox Code Playgroud)
我真的不知道它做了什么,因为__node已经分配了空间.但在此之后它也会这样做
_Alloc_traits::construct(_M_get_Node_allocator(), __node->_M_valptr(), ...);
Run Code Online (Sandbox Code Playgroud)
这让我更加困惑,因为它应该构建一个节点(是节点分配器),但是它传递了__node->_M_valptr()类型的指针_Val*.
如果有人能够解释这一点,我将非常感激.
我正在使用外部库(pcl),所以我需要一个不会改变现有函数原型的解决方案.
我正在使用的一个功能生成一个std::vector<int, Eigen::aligned_allocator<int>>.我想要接下来调用的函数需要a const boost::shared_ptr<std::vector<int, std::allocator<int>>>.我不想复制元素,因为它是我代码中已经很慢的关键部分.如果不是因为分配器不匹配,我只需执行以下操作即可绕过shared_ptr要求:
// code that generates std::vector<int, Eigen::aligned_allocator<int>> source
boost::shared_ptr<std::vector<int>> indices(new std::vector<int>);
indices->swap(source);
// use indices as intended
Run Code Online (Sandbox Code Playgroud)
这不能使用MSVC编译器进行编译,因为它无法在这两种向量类型之间进行转换.到目前为止,我所想到的唯一不会复制内容的解决方案是:
// code that generates std::vector<int, Eigen::aligned_allocator<int>> source
boost::shared_ptr<std::vector<int>> indices(new std::vector<int>);
indices->swap(reinterpret_cast<std::vector<int>&>(source));
// use indices as intended
indices->swap(reinterpret_cast<std::vector<int>&>(pcout.points));
Run Code Online (Sandbox Code Playgroud)
注意我需要如何使用索引作为const shared_ptr.我相信分配器不会在交换操作中发挥作用.int也应该不需要对齐任何填充,因为它们已经是32位大小.std :: allocator版本应该能够从对齐版本中读取,因为它只能分配std :: allocator可能已经使用过的内存地址.最后,我换回,因为如果尝试删除未对齐的保留空间,则对齐的分配器可能会崩溃.
我尝试了它并没有崩溃,但这并不是说服我确实它是正确的.安全吗?如果没有,如果对编译器做出某些合理的假设,它是否有条件安全?有没有更明显影响性能的更安全的替代方案?
请不要回复"描述您的代码","不值得"或类似的答案.即使它们适用于此,理论上也存在复制不是可行解决方案的情况,并且该线程应该解决这个问题.
类似的问题是谈论以清晰的方式复制数据,如评论中所阐明的那样.
编辑:似乎尽管Eigen :: aligned_allocator设计用于16位对齐,但没有额外的填充添加到整数.比较列表中第一个和最后一个元素的地址给出了元素数量和sizeof(int)的大小.这意味着int以一种应该与std :: allocator版本兼容的方式存储.我希望我今天晚些时候或在未来几天有时间做一个更完整的测试.
分配器要求的cppreference页没有说分配器必须是可继承的,即没有说分配器一定不能是最终的。
但是,在许多库中,分配器是私有继承的,以利用无状态分配器的空基类优化。例如:
template <typename T, typename A = std::allocator<T>>
class Dummy_vector :private A {
// ...
A get_alloc() const
{
return static_cast<A>(*this);
}
// ...
};
Run Code Online (Sandbox Code Playgroud)
如果A是最终的,则该实现会中断。
一个可以分配器是最终的?我错过了什么?还是应该为最终分配器包括特殊代码?
(注意:通过“最终分配器的特殊代码”,我的意思是这样的:
template <
typename T,
typename A = std::allocator<T>,
bool = std::is_final<A>
>
class Dummy_vector :private A {
// version for nonfinal allocators
// ...
A get_alloc() const
{
return static_cast<A>(*this);
}
// ...
};
template …Run Code Online (Sandbox Code Playgroud) allocator ×10
c++ ×10
stl ×3
boost ×2
vector ×2
c++11 ×1
c++17 ×1
c++pmr ×1
final ×1
hint ×1
inheritance ×1
parameters ×1
std ×1
type-erasure ×1