为什么我不能将 T* 包裹在 std::vector<T> 中?
Why can't I wrap a T* in an std::vector<T>?
我有一个 T*
使用类型 T
的 len
个元素寻址缓冲区。出于某些原因,我需要 std::vector<T>
形式的数据。据我所知,我无法构建一个使用我的缓冲区作为其内部存储的向量。这是为什么?
备注:
- 请不要建议我使用迭代器 - 我知道这通常是解决此类问题的方法。
- 我不介意向量在稍后调整大小时必须复制数据。
- 这个问题尤其让我感到困惑,因为 C++ 具有移动语义。如果我们可以从脚下拉出一个对象的存储空间,为什么不能推入我们自己的存储空间?
简短的回答是向量不能使用你的缓冲区,因为它不是那样设计的。
这也有道理。如果 vector 不分配自己的内存,那么当添加更多项时它如何调整缓冲区的大小?它分配了一个新缓冲区,但是它对旧缓冲区有什么作用呢?这同样适用于移动——如果向量不控制它自己的缓冲区,它怎么能把这个缓冲区的控制权交给另一个实例呢?
可以。
您写的是 std::vector<T>
,但是 std::vector
需要 两个 模板参数,而不仅仅是一个。第二个模板参数指定要使用的分配器类型,vector
的构造函数具有允许传入该分配器类型的自定义实例的重载。
所以您需要做的就是编写一个尽可能使用您自己的内部缓冲区的分配器,并在您自己的内部缓冲区已满时返回询问默认分配器。
默认分配器不可能处理它,因为它不知道哪些内存位可以被释放,哪些不能。
带有内部缓冲区的示例状态分配器,其中包含不应被向量覆盖的已构建元素,包括一个大陷阱的演示:
struct my_allocator_state {
void *buf;
std::size_t len;
bool bufused;
const std::type_info *type;
};
template <typename T>
struct my_allocator {
typedef T value_type;
my_allocator(T *buf, std::size_t len)
: def(), state(std::make_shared<my_allocator_state, my_allocator_state>({ buf, len, false, &typeid(T) })) { }
template <std::size_t N>
my_allocator(T(&buf)[N])
: def(), state(std::make_shared<my_allocator_state, my_allocator_state>({ buf, N, false, &typeid(T) })) { }
template <typename U>
friend struct my_allocator;
template <typename U>
my_allocator(my_allocator<U> other)
: def(), state(other.state) { }
T *allocate(std::size_t n)
{
if (!state->bufused && n == state->len && typeid(T) == *state->type)
{
state->bufused = true;
return static_cast<T *>(state->buf);
}
else
return def.allocate(n);
}
void deallocate(T *p, std::size_t n)
{
if (p == state->buf)
state->bufused = false;
else
def.deallocate(p, n);
}
template <typename...Args>
void construct(T *c, Args... args)
{
if (!in_buffer(c))
def.construct(c, std::forward<Args>(args)...);
}
void destroy(T *c)
{
if (!in_buffer(c))
def.destroy(c);
}
friend bool operator==(const my_allocator &a, const my_allocator &b) {
return a.state == b.state;
}
friend bool operator!=(const my_allocator &a, const my_allocator &b) {
return a.state != b.state;
}
private:
std::allocator<T> def;
std::shared_ptr<my_allocator_state> state;
bool in_buffer(T *p) {
return *state->type == typeid(T)
&& points_into_buffer(p, static_cast<T *>(state->buf), state->len);
}
};
int main()
{
int buf [] = { 1, 2, 3, 4 };
std::vector<int, my_allocator<int>> v(sizeof buf / sizeof *buf, {}, buf);
v.resize(3);
v.push_back(5);
v.push_back(6);
for (auto &i : v) std::cout << i << std::endl;
}
输出:
1
2
3
4
6
5
的 push_back
适合旧缓冲区,因此构造被绕过。添加 6
时,分配新内存,一切开始正常运行。您可以通过向分配器添加一个方法来避免该问题,以指示从那时起,不应再绕过构造。
points_into_buffer
结果是最难写的部分,我在回答中省略了它。从我的使用方式来看,预期的语义应该是显而易见的。请在我的回答中查看 的可移植实现,或者如果您的实现允许,请在另一个问题中使用一个更简单的版本。
顺便说一句,我对某些实现如何以无法避免存储 运行 时间类型信息和状态的方式使用 rebind
不太满意,但是如果您的实现不需要它,您也可以通过将状态设为模板 class(或嵌套 class)来使其更简单。
最近 - 您不再需要将 T*
包裹在 std::vector
中,您可以将其包裹在 std::span
中(在 C++20 中;在此之前 - 使用 gsl::span
)。 span 为您提供标准库容器的所有便利 - 事实上,基本上 std::vector
的所有相关功能不包括大小更改 - 具有非常薄的包装器 class。这就是你想要使用的,真的。
有关跨度的更多信息,请阅读:
我有一个 T*
使用类型 T
的 len
个元素寻址缓冲区。出于某些原因,我需要 std::vector<T>
形式的数据。据我所知,我无法构建一个使用我的缓冲区作为其内部存储的向量。这是为什么?
备注:
- 请不要建议我使用迭代器 - 我知道这通常是解决此类问题的方法。
- 我不介意向量在稍后调整大小时必须复制数据。
- 这个问题尤其让我感到困惑,因为 C++ 具有移动语义。如果我们可以从脚下拉出一个对象的存储空间,为什么不能推入我们自己的存储空间?
简短的回答是向量不能使用你的缓冲区,因为它不是那样设计的。
这也有道理。如果 vector 不分配自己的内存,那么当添加更多项时它如何调整缓冲区的大小?它分配了一个新缓冲区,但是它对旧缓冲区有什么作用呢?这同样适用于移动——如果向量不控制它自己的缓冲区,它怎么能把这个缓冲区的控制权交给另一个实例呢?
可以。
您写的是 std::vector<T>
,但是 std::vector
需要 两个 模板参数,而不仅仅是一个。第二个模板参数指定要使用的分配器类型,vector
的构造函数具有允许传入该分配器类型的自定义实例的重载。
所以您需要做的就是编写一个尽可能使用您自己的内部缓冲区的分配器,并在您自己的内部缓冲区已满时返回询问默认分配器。
默认分配器不可能处理它,因为它不知道哪些内存位可以被释放,哪些不能。
带有内部缓冲区的示例状态分配器,其中包含不应被向量覆盖的已构建元素,包括一个大陷阱的演示:
struct my_allocator_state {
void *buf;
std::size_t len;
bool bufused;
const std::type_info *type;
};
template <typename T>
struct my_allocator {
typedef T value_type;
my_allocator(T *buf, std::size_t len)
: def(), state(std::make_shared<my_allocator_state, my_allocator_state>({ buf, len, false, &typeid(T) })) { }
template <std::size_t N>
my_allocator(T(&buf)[N])
: def(), state(std::make_shared<my_allocator_state, my_allocator_state>({ buf, N, false, &typeid(T) })) { }
template <typename U>
friend struct my_allocator;
template <typename U>
my_allocator(my_allocator<U> other)
: def(), state(other.state) { }
T *allocate(std::size_t n)
{
if (!state->bufused && n == state->len && typeid(T) == *state->type)
{
state->bufused = true;
return static_cast<T *>(state->buf);
}
else
return def.allocate(n);
}
void deallocate(T *p, std::size_t n)
{
if (p == state->buf)
state->bufused = false;
else
def.deallocate(p, n);
}
template <typename...Args>
void construct(T *c, Args... args)
{
if (!in_buffer(c))
def.construct(c, std::forward<Args>(args)...);
}
void destroy(T *c)
{
if (!in_buffer(c))
def.destroy(c);
}
friend bool operator==(const my_allocator &a, const my_allocator &b) {
return a.state == b.state;
}
friend bool operator!=(const my_allocator &a, const my_allocator &b) {
return a.state != b.state;
}
private:
std::allocator<T> def;
std::shared_ptr<my_allocator_state> state;
bool in_buffer(T *p) {
return *state->type == typeid(T)
&& points_into_buffer(p, static_cast<T *>(state->buf), state->len);
}
};
int main()
{
int buf [] = { 1, 2, 3, 4 };
std::vector<int, my_allocator<int>> v(sizeof buf / sizeof *buf, {}, buf);
v.resize(3);
v.push_back(5);
v.push_back(6);
for (auto &i : v) std::cout << i << std::endl;
}
输出:
1 2 3 4 6
5
的 push_back
适合旧缓冲区,因此构造被绕过。添加 6
时,分配新内存,一切开始正常运行。您可以通过向分配器添加一个方法来避免该问题,以指示从那时起,不应再绕过构造。
points_into_buffer
结果是最难写的部分,我在回答中省略了它。从我的使用方式来看,预期的语义应该是显而易见的。请在我的回答中查看
顺便说一句,我对某些实现如何以无法避免存储 运行 时间类型信息和状态的方式使用 rebind
不太满意,但是如果您的实现不需要它,您也可以通过将状态设为模板 class(或嵌套 class)来使其更简单。
最近 - 您不再需要将 T*
包裹在 std::vector
中,您可以将其包裹在 std::span
中(在 C++20 中;在此之前 - 使用 gsl::span
)。 span 为您提供标准库容器的所有便利 - 事实上,基本上 std::vector
的所有相关功能不包括大小更改 - 具有非常薄的包装器 class。这就是你想要使用的,真的。
有关跨度的更多信息,请阅读: