【问题标题】:Recasting a container of void pointers重铸一个空指针容器
【发布时间】:2018-04-07 06:01:18
【问题描述】:

短版

我可以将reinterpret_caststd::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::liststd::map)怎么办?明确地说,我的意思是将 std::list&lt;void*&gt; 转换为 std::list&lt;T*&gt;,而不是在 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

参考严格的别名规则,这些问题的答案是一致的“否”。但我认为这不适用于我的情况,因为被重铸的向量是一个右值,所以没有混叠的机会。

我为什么要这样做

我正在与一个 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;
    }
}

【问题讨论】:

  • processData&lt;float&gt; 仍然可以接受vector&lt;void *&gt; 并在需要的时候将void * 隐式转换为float *。还可以考虑对processData 使用开始-结束范围而不是向量(尽管这不能解决问题)

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


【解决方案1】:

鉴于您收到来自 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';
    }
}

请注意,这根本不会尝试处理复制/移动构造,也不会尝试破坏。至少按照我的表述,array_view 是对某些现有数据的非拥有视图。在适当的时候销毁数据取决于你(或者至少是array_view 之外的东西)。由于我们没有破坏数据,因此我们可以使用编译器生成的副本并移动构造函数而没有任何问题。我们不会从指针的浅拷贝中得到双重删除,因为当array_view 被销毁时,我们不会进行任何删除。

【讨论】:

  • 就我而言,我不会将void * 重新解释为int[],而是将void * 的容器转换为int * 的容器。对于我的用例(给定一个std::vector&lt;void *&gt; voidPtrs;),构造一个array_view&lt;int *&gt; typedPtrs(voidPtrs.data(), voidPtrs.size()) 是否公平(关于破坏原始voidPtrs 向量的警告)?
  • @KQS:在相当多的情况下,您可能会侥幸逃脱,但可以这么说,这并不是真正的犹太教。对于某些特定的T,完全允许std::vector&lt;T&gt; 成为与普通vector 完全不同的专业化。最明显的例子是std::vector&lt;bool&gt;,它(通常)与std::vector&lt;int&gt; 完全不同,即使boolint 的大小相同。
  • P.S.您认为这是解决此问题的适当方法吗:stackoverflow.com/questions/2434196/…
  • @KQS:也许吧。如果他真的只是想要对原始数据进行只读访问,那么是的,这可以工作(并且与将数据复制到向量中的几乎任何东西相比,显然会减少开销)。如果他想将结果用作真实向量,他可能(例如)使用push_backresize 将更多元素添加到集合中,那么他有点坚持使用真实向量(或一些存储一个指向现有数据的指针、新元素的空间和一个用于跟踪位置的索引——如果元素很大/复制成本很高,这可能是合理的)。
【解决方案2】:

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

严格的别名规则说要访问T类型的对象,你必须使用T类型的表达式;有一个非常简短的例外列表。

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

【讨论】:

  • 我从 C 库中得到这些 void*(类似于 malloc);那么与他们合作的推荐方式是什么?
  • @KQS 您的问题中没有足够的信息来回答这个问题。也许你不需要实际创建double *的向量
  • 我不需要维护这两种类型的向量(这就是我试图对右值提出的观点)。一旦我将它们从void* 转换为double* 我就完成了void* 向量
  • @KQS 一个 C 库首先不会返回 std::vector&lt;void*&gt;
  • 我在我的问题中添加了更多信息来解释这背后的动机。
猜你喜欢
  • 2011-03-26
  • 2011-04-03
  • 2020-01-17
  • 1970-01-01
  • 2010-12-27
  • 2012-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多