重铸空指针容器
Recasting a container of void pointers
短版
我可以reinterpret_cast
一个std::vector<void*>*
到一个std::vector<double*>*
吗?
其他 STL 容器呢?
长版
我有一个函数可以将 void 指针的向量重铸为由模板参数指定的数据类型:
template <typename T>
std::vector<T*> recastPtrs(std::vector<void*> const& x) {
std::vector<T*> y(x.size());
std::transform(x.begin(), x.end(), y.begin(),
[](void *a) { return static_cast<T*>(a); } );
return y;
}
但我认为复制矢量内容并不是真正必要的,因为我们实际上只是在重新解释所指向的内容。
经过一些修补,我想到了这个:
template <typename T>
std::vector<T*> recastPtrs(std::vector<void*>&& x) {
auto xPtr = reinterpret_cast<std::vector<T*>*>(&x);
return std::vector<T*>(std::move(*xPtr));
}
所以我的问题是:
- 像这样 reinterpret_cast 整个向量安全吗?
- 如果它是另一种容器(例如
std::list
或 std::map
)怎么办?明确地说,我的意思是将 std::list<void*>
转换为 std::list<T*>
,而不是在 STL 容器类型之间进行转换。
- 我仍在努力思考移动语义。我做对了吗?
还有一个后续问题:在没有代码重复的情况下生成 const
版本的最佳方法是什么?即定义
std::vector<T const*> recastPtrs(std::vector<void const*> const&);
std::vector<T const*> recastPtrs(std::vector<void const*>&&);
MWE
#include <vector>
#include <algorithm>
#include <iostream>
template <typename T>
std::vector<T*> recastPtrs(std::vector<void*> const& x) {
std::vector<T*> y(x.size());
std::transform(x.begin(), x.end(), y.begin(),
[](void *a) { return static_cast<T*>(a); } );
return y;
}
template <typename T>
std::vector<T*> recastPtrs(std::vector<void*>&& x) {
auto xPtr = reinterpret_cast<std::vector<T*>*>(&x);
return std::vector<T*>(std::move(*xPtr));
}
template <typename T>
void printVectorAddr(std::vector<T> const& vec) {
std::cout<<" vector object at "<<&vec<<", data()="<<vec.data()<<std::endl;
}
int main(void) {
std::cout<<"Original void pointers"<<std::endl;
std::vector<void*> voidPtrs(100);
printVectorAddr(voidPtrs);
std::cout<<"Elementwise static_cast"<<std::endl;
auto dblPtrs = recastPtrs<double>(voidPtrs);
printVectorAddr(dblPtrs);
std::cout<<"reintepret_cast entire vector, then move ctor"<<std::endl;
auto dblPtrs2 = recastPtrs<double>(std::move(voidPtrs));
printVectorAddr(dblPtrs2);
}
示例输出:
Original void pointers
vector object at 0x7ffe230b1cb0, data()=0x21de030
Elementwise static_cast
vector object at 0x7ffe230b1cd0, data()=0x21de360
reintepret_cast entire vector, then move ctor
vector object at 0x7ffe230b1cf0, data()=0x21de030
请注意,reinterpret_cast
版本重用了底层数据结构。
之前提出的似乎不相关的问题
这些是我尝试搜索时出现的问题:
reinterpret_cast-ing vector of one type to a vector of another type which is of the same type
参考严格的别名规则,对这些问题的回答是一致的“否”。但我认为这不适用于我的情况,因为被重铸的向量是一个右值,所以没有混叠的机会。
我为什么要这样做
我正在与一个 MATLAB 库进行交互,该库为我提供 void*
形式的数据指针以及一个指示数据类型的变量。我有一个函数可以验证输入并将这些指针收集到一个向量中:
void parseInputs(int argc, mxArray* inputs[], std::vector<void*> &dataPtrs, mxClassID &numericType);
我无法将这部分模板化,因为直到运行时才知道类型。另一方面,我有数字例程来对已知数据类型的向量进行操作:
template <typename T>
void processData(std::vector<T*> const& dataPtrs);
所以我只是想把一个连接到另一个:
void processData(std::vector<void*>&& voidPtrs, mxClassID numericType) {
switch (numericType) {
case mxDOUBLE_CLASS:
processData(recastPtrs<double>(std::move(voidPtrs)));
break;
case mxSINGLE_CLASS:
processData(recastPtrs<float>(std::move(voidPtrs)));
break;
default:
assert(0 && "Unsupported datatype");
break;
}
}
不,您不能在标准 C++ 中执行此类操作。
严格的别名规则规定,要访问 T
类型的对象,您必须使用 T
类型的表达式;有一个非常简短的例外列表。
通过 void *
表达式访问 double *
也不例外;更不用说每个向量了。如果您通过右值访问 T
类型的对象也不例外。
鉴于您收到来自 C 库(类似于 malloc)的 void *
的评论,看来我们可以将问题缩小很多。
特别是,我猜你真的在处理更像 array_view
而不是 vector
的东西。也就是说,您需要能够让您 访问 一些数据的东西。您可能会更改该集合中的个别项目,但您永远不会更改整个集合(例如,您不会尝试执行可能需要扩展内存分配的 push_back
)。
对于这种情况,您可以很容易地创建自己的包装器,使您可以像向量一样访问数据——定义一个 iterator
类型,具有 begin()
和 end()
(如果你愿意,其他人如 rbegin()
/rend()
、cbegin()
/cend()
和 crbegin()
/crend()
),以及进行范围检查索引等的 at()
。
所以一个相当 最小的 版本可能看起来像这样:
#pragma once
#include <cstddef>
#include <stdexcept>
#include <cstdlib>
#include <iterator>
template <class T> // note: no allocator, since we don't do allocation
class array_view {
T *data;
std::size_t size_;
public:
array_view(void *data, std::size_t size_) : data(reinterpret_cast<T *>(data)), size_(size_) {}
T &operator[](std::size_t index) { return data[index]; }
T &at(std::size_t index) {
if (index > size_) throw std::out_of_range("Index out of range");
return data[index];
}
std::size_t size() const { return size_; }
typedef T *iterator;
typedef T const &const_iterator;
typedef T value_type;
typedef T &reference;
iterator begin() { return data; }
iterator end() { return data + size_; }
const_iterator cbegin() { return data; }
const_iterator cend() { return data + size_; }
class reverse_iterator {
T *it;
public:
reverse_iterator(T *it) : it(it) {}
using iterator_category = std::random_access_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T *;
using reference = T &;
reverse_iterator &operator++() {
--it;
return *this;
}
reverse_iterator &operator--() {
++it;
return *this;
}
reverse_iterator operator+(size_t size) const {
return reverse_iterator(it - size);
}
reverse_iterator operator-(size_t size) const {
return reverse_iterator(it + size);
}
difference_type operator-(reverse_iterator const &r) const {
return it - r.it;
}
bool operator==(reverse_iterator const &r) const { return it == r.it; }
bool operator!=(reverse_iterator const &r) const { return it != r.it; }
bool operator<(reverse_iterator const &r) const { return std::less<T*>(r.it, it); }
bool operator>(reverse_iterator const &r) const { return std::less<T*>(it, r.it); }
T &operator *() { return *(it-1); }
};
reverse_iterator rbegin() { return data + size_; }
reverse_iterator rend() { return data; }
};
我已经尽力展示了如何添加大部分缺失的功能(例如,crbegin()
/crend()
),但我并没有真正努力在这里包括所有内容,因为剩下的大部分内容比教育更重复和乏味。
这足以在大多数典型的类似向量的方式中使用 array_view
。例如:
#include "array_view"
#include <iostream>
#include <iterator>
int main() {
void *raw = malloc(16 * sizeof(int));
array_view<int> data(raw, 16);
std::cout << "Range based:\n";
for (auto & i : data)
i = rand();
for (auto const &i : data)
std::cout << i << '\n';
std::cout << "\niterator-based, reverse:\n";
auto end = data.rend();
for (auto d = data.rbegin(); d != end; ++d)
std::cout << *d << '\n';
std::cout << "Forward, counted:\n";
for (int i=0; i<data.size(); i++) {
data[i] += 10;
std::cout << data[i] << '\n';
}
}
请注意,这根本不会尝试处理 copy/move 构造,也不会处理破坏。至少按照我的表述,array_view
是对某些现有数据的非拥有视图。在适当的时候销毁数据取决于您(或者至少 array_view
之外的其他人)。因为我们没有破坏数据,所以我们可以毫无问题地使用编译器生成的复制和移动构造函数。我们不会通过指针的浅拷贝获得双重删除,因为当 array_view
被销毁时我们不会执行 any 删除。
短版
我可以reinterpret_cast
一个std::vector<void*>*
到一个std::vector<double*>*
吗?
其他 STL 容器呢?
长版
我有一个函数可以将 void 指针的向量重铸为由模板参数指定的数据类型:
template <typename T>
std::vector<T*> recastPtrs(std::vector<void*> const& x) {
std::vector<T*> y(x.size());
std::transform(x.begin(), x.end(), y.begin(),
[](void *a) { return static_cast<T*>(a); } );
return y;
}
但我认为复制矢量内容并不是真正必要的,因为我们实际上只是在重新解释所指向的内容。
经过一些修补,我想到了这个:
template <typename T>
std::vector<T*> recastPtrs(std::vector<void*>&& x) {
auto xPtr = reinterpret_cast<std::vector<T*>*>(&x);
return std::vector<T*>(std::move(*xPtr));
}
所以我的问题是:
- 像这样 reinterpret_cast 整个向量安全吗?
- 如果它是另一种容器(例如
std::list
或std::map
)怎么办?明确地说,我的意思是将std::list<void*>
转换为std::list<T*>
,而不是在 STL 容器类型之间进行转换。 - 我仍在努力思考移动语义。我做对了吗?
还有一个后续问题:在没有代码重复的情况下生成 const
版本的最佳方法是什么?即定义
std::vector<T const*> recastPtrs(std::vector<void const*> const&);
std::vector<T const*> recastPtrs(std::vector<void const*>&&);
MWE
#include <vector>
#include <algorithm>
#include <iostream>
template <typename T>
std::vector<T*> recastPtrs(std::vector<void*> const& x) {
std::vector<T*> y(x.size());
std::transform(x.begin(), x.end(), y.begin(),
[](void *a) { return static_cast<T*>(a); } );
return y;
}
template <typename T>
std::vector<T*> recastPtrs(std::vector<void*>&& x) {
auto xPtr = reinterpret_cast<std::vector<T*>*>(&x);
return std::vector<T*>(std::move(*xPtr));
}
template <typename T>
void printVectorAddr(std::vector<T> const& vec) {
std::cout<<" vector object at "<<&vec<<", data()="<<vec.data()<<std::endl;
}
int main(void) {
std::cout<<"Original void pointers"<<std::endl;
std::vector<void*> voidPtrs(100);
printVectorAddr(voidPtrs);
std::cout<<"Elementwise static_cast"<<std::endl;
auto dblPtrs = recastPtrs<double>(voidPtrs);
printVectorAddr(dblPtrs);
std::cout<<"reintepret_cast entire vector, then move ctor"<<std::endl;
auto dblPtrs2 = recastPtrs<double>(std::move(voidPtrs));
printVectorAddr(dblPtrs2);
}
示例输出:
Original void pointers
vector object at 0x7ffe230b1cb0, data()=0x21de030
Elementwise static_cast
vector object at 0x7ffe230b1cd0, data()=0x21de360
reintepret_cast entire vector, then move ctor
vector object at 0x7ffe230b1cf0, data()=0x21de030
请注意,reinterpret_cast
版本重用了底层数据结构。
之前提出的似乎不相关的问题
这些是我尝试搜索时出现的问题:
reinterpret_cast-ing vector of one type to a vector of another type which is of the same type
参考严格的别名规则,对这些问题的回答是一致的“否”。但我认为这不适用于我的情况,因为被重铸的向量是一个右值,所以没有混叠的机会。
我为什么要这样做
我正在与一个 MATLAB 库进行交互,该库为我提供 void*
形式的数据指针以及一个指示数据类型的变量。我有一个函数可以验证输入并将这些指针收集到一个向量中:
void parseInputs(int argc, mxArray* inputs[], std::vector<void*> &dataPtrs, mxClassID &numericType);
我无法将这部分模板化,因为直到运行时才知道类型。另一方面,我有数字例程来对已知数据类型的向量进行操作:
template <typename T>
void processData(std::vector<T*> const& dataPtrs);
所以我只是想把一个连接到另一个:
void processData(std::vector<void*>&& voidPtrs, mxClassID numericType) {
switch (numericType) {
case mxDOUBLE_CLASS:
processData(recastPtrs<double>(std::move(voidPtrs)));
break;
case mxSINGLE_CLASS:
processData(recastPtrs<float>(std::move(voidPtrs)));
break;
default:
assert(0 && "Unsupported datatype");
break;
}
}
不,您不能在标准 C++ 中执行此类操作。
严格的别名规则规定,要访问 T
类型的对象,您必须使用 T
类型的表达式;有一个非常简短的例外列表。
通过 void *
表达式访问 double *
也不例外;更不用说每个向量了。如果您通过右值访问 T
类型的对象也不例外。
鉴于您收到来自 C 库(类似于 malloc)的 void *
的评论,看来我们可以将问题缩小很多。
特别是,我猜你真的在处理更像 array_view
而不是 vector
的东西。也就是说,您需要能够让您 访问 一些数据的东西。您可能会更改该集合中的个别项目,但您永远不会更改整个集合(例如,您不会尝试执行可能需要扩展内存分配的 push_back
)。
对于这种情况,您可以很容易地创建自己的包装器,使您可以像向量一样访问数据——定义一个 iterator
类型,具有 begin()
和 end()
(如果你愿意,其他人如 rbegin()
/rend()
、cbegin()
/cend()
和 crbegin()
/crend()
),以及进行范围检查索引等的 at()
。
所以一个相当 最小的 版本可能看起来像这样:
#pragma once
#include <cstddef>
#include <stdexcept>
#include <cstdlib>
#include <iterator>
template <class T> // note: no allocator, since we don't do allocation
class array_view {
T *data;
std::size_t size_;
public:
array_view(void *data, std::size_t size_) : data(reinterpret_cast<T *>(data)), size_(size_) {}
T &operator[](std::size_t index) { return data[index]; }
T &at(std::size_t index) {
if (index > size_) throw std::out_of_range("Index out of range");
return data[index];
}
std::size_t size() const { return size_; }
typedef T *iterator;
typedef T const &const_iterator;
typedef T value_type;
typedef T &reference;
iterator begin() { return data; }
iterator end() { return data + size_; }
const_iterator cbegin() { return data; }
const_iterator cend() { return data + size_; }
class reverse_iterator {
T *it;
public:
reverse_iterator(T *it) : it(it) {}
using iterator_category = std::random_access_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T *;
using reference = T &;
reverse_iterator &operator++() {
--it;
return *this;
}
reverse_iterator &operator--() {
++it;
return *this;
}
reverse_iterator operator+(size_t size) const {
return reverse_iterator(it - size);
}
reverse_iterator operator-(size_t size) const {
return reverse_iterator(it + size);
}
difference_type operator-(reverse_iterator const &r) const {
return it - r.it;
}
bool operator==(reverse_iterator const &r) const { return it == r.it; }
bool operator!=(reverse_iterator const &r) const { return it != r.it; }
bool operator<(reverse_iterator const &r) const { return std::less<T*>(r.it, it); }
bool operator>(reverse_iterator const &r) const { return std::less<T*>(it, r.it); }
T &operator *() { return *(it-1); }
};
reverse_iterator rbegin() { return data + size_; }
reverse_iterator rend() { return data; }
};
我已经尽力展示了如何添加大部分缺失的功能(例如,crbegin()
/crend()
),但我并没有真正努力在这里包括所有内容,因为剩下的大部分内容比教育更重复和乏味。
这足以在大多数典型的类似向量的方式中使用 array_view
。例如:
#include "array_view"
#include <iostream>
#include <iterator>
int main() {
void *raw = malloc(16 * sizeof(int));
array_view<int> data(raw, 16);
std::cout << "Range based:\n";
for (auto & i : data)
i = rand();
for (auto const &i : data)
std::cout << i << '\n';
std::cout << "\niterator-based, reverse:\n";
auto end = data.rend();
for (auto d = data.rbegin(); d != end; ++d)
std::cout << *d << '\n';
std::cout << "Forward, counted:\n";
for (int i=0; i<data.size(); i++) {
data[i] += 10;
std::cout << data[i] << '\n';
}
}
请注意,这根本不会尝试处理 copy/move 构造,也不会处理破坏。至少按照我的表述,array_view
是对某些现有数据的非拥有视图。在适当的时候销毁数据取决于您(或者至少 array_view
之外的其他人)。因为我们没有破坏数据,所以我们可以毫无问题地使用编译器生成的复制和移动构造函数。我们不会通过指针的浅拷贝获得双重删除,因为当 array_view
被销毁时我们不会执行 any 删除。