重新构建一个void指针容器

时间:2017-10-26 01:23:21

标签: c++ c++11 casting stl move-semantics

短版

我可以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));
}

所以我的问题是:

  • 像这样重新解释整个矢量是否安全?
  • 如果它是一种不同类型的容器(如std::liststd::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 vector of class A to vector of class B

reinterpret_cast vector of derived class to vector of base class

reinterpret_cast-ing vector of one type to a vector of another type which is of the same type

这些答案是一致的NO,参考严格的别名规则。但我觉得这并不适用于我的情况,因为重铸的矢量是一个右值,因此没有混叠的机会。

为什么我要尝试这样做

我与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;
    }
}

2 个答案:

答案 0 :(得分:2)

鉴于您从C库(类似malloc)收到void *的评论,似乎我们可以将问题缩小到相当多的范围。

特别是,我猜你真的在处理的事情更像是array_view而不是vector。也就是说,您需要能够干净地访问某些数据的内容。您可以更改该集合中的单个项目,但是您永远不会更改整个集合(例如,您不会尝试执行可能需要扩展内存分配的push_back)。

对于这种情况,您可以非常轻松地创建自己的包装器,为您提供类似于矢量的数据访问 - 定义iterator类型,具有begin()和{{1 (如果你愿意,其他人如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'; } } 是对一些现有数据的非拥有视图。它取决于您(或至少在array_view之外的某些内容)在适当时销毁数据。由于我们不会破坏数据,因此我们可以使用编译器生成的副本并移动构造函数而不会出现任何问题。我们不会在执行指针的浅层复制时获得双重删除,因为在array_view被销毁时我们不会执行任何删除。

答案 1 :(得分:0)

不,你不能在标准C ++中做这样的事情。

严格别名规则表示要访问T类型的对象,必须使用类型为T的表达式;有一个很短的例外清单。

通过double *表达式访问void *不是一个例外;更不用说每个人的矢量了。如果您通过右值访问T类型的对象,也不例外。