【问题标题】:How can i create a istream from a uint8_t vector?如何从 uint8_t 向量创建 istream?
【发布时间】:2023-03-06 21:00:01
【问题描述】:

我正在将通过网络获取数据的功能添加到过去只读取本地文件的代码中。我使用的网络库以vector<uint8_t> 的形式发送和接收数据。我希望能够在读取文件后重用处理数据的代码,但是该代码需要 std::istream,有没有办法让 istream 读取矢量数据?这是相同的数据,所以我觉得应该有一种方法,但我无法找到或弄清楚如何做到这一点的代码。

当前代码:

    std::ifstream stream("data.img", std::ios::in | std::ios::binary | std::ios::ate);

    if (!stream.is_open())
    {
        throw std::invalid_argument("Could not open file.");
    }
    // the arg for processData is std::istream
    processData(stream);

网络框架:

    vector<uint8_t> data = networkMessage.data;

    // need some way to create istream from data
    std::istream stream = ?

    processData(stream);
    stream.close();

有没有办法做到这一点,还是我找错树了?

【问题讨论】:

标签: c++


【解决方案1】:

std::basic_istream 从关联的std::basic_streambuf 派生类中获取其数据。 STL 为文件 I/O 和字符串 I/O 提供此类类,但不为内存 I/O 或网络 I/O 提供此类。

您可以轻松编写(或找到第 3 方)使用 std::vector 作为其底层缓冲区的基于内存的 streambuf 类,然后您可以构造一个使用该内存 streambufstd::istream。例如(使用 imemstreamthis answer):

std::vector<uint8_t> &data = networkMessage.data;
imemstream stream(reinterpret_cast<const char*>(data.data()), data.size());
processData(stream);

【讨论】:

    【解决方案2】:

    C++ 实际上有一个用于此的类 - istrstream,您可以像这样使用它:

        vector<uint8_t> data = ...;
    
        // need some way to create istream from data
        std::istrstream stream(reinterpret_cast<const char*>(data.data()), data.size());
    
        processData(stream);
    

    据我所知,与其他答案不同,这不会复制数据。然而,它在 C++98 because it's hard to know who is responsible for freeing the buffer 中也已被弃用,因此您可能需要编写自己的等价物。

    【讨论】:

    • 那是獾(虽然弃用让我害怕,我个人使用 Remy 链接到的解决方案)
    • 这允许与其他解决方案不同的搜索。
    【解决方案3】:
    1. istream 是原始数据的引用。它不保存数据,只是一个访问者,通过保留一些char* 指向数据内存地址开始和结束的指针。

    2. vector中的存储是连续的,但是使用push_back()可能会改变存储地址,(复制的内部vector)

    3. 因此可以将istream 设为const vector

    参考文献

    https://en.cppreference.com/w/cpp/io/basic_istream https://www.cplusplus.com/reference/streambuf/streambuf/

    最短的例子

    class vectorbuf : public std::streambuf {
    public:
        vectorbuf(std::vector<uint8_t> &v){
            setg((char*)v.data(), (char*)v.data(), (char*)(v.data() + v.size()));
        }
        ~vectorbuf() {}
    };
    
    //Usage:
    vector<uint8_t>  arr{11,12,13,14,15,16};
    vectorbuf vbuf(arr);
    std::istream is(&vbuf);
    

    完整的错误示例代码

    #include <streambuf>
    #include <iostream>
    #include <iomanip>
    #include <vector>
    
    using namespace std;
    
    
    template<typename T>
    class vectorbuf : public std::streambuf {
    public:
        vectorbuf(std::vector<T> &v) : _value(v) {
            char *bptr = (char*)_value.data();
            char *eptr = (char*)(_value.data() + _value.size());
            setg(bptr, bptr, eptr);
            cout<<"Setg: "<<(void*)bptr<<" "<<(void*)eptr<<endl;
        }
        ~vectorbuf() {}
    
    //Zone start ---
    //Note: this zone of code can be commented since the virtual function in base class do same 
    protected:
        virtual int underflow() {
            char *bptr = (char*)_value.data();
            char *new_eptr = (char*)(_value.data() + _value.size());
            cout<<"[underflow() when gptr()="<<(void*)gptr()
                <<", now_bptr="<<(void*)bptr<<" now_eptr="<<(void*)new_eptr<<"]";
    
            return traits_type::eof();
            //since the vector& must not modified, the code below is unnecessary.
            if (new_eptr == egptr())
                return traits_type::eof();
            setg(bptr, gptr(), new_eptr);
            return *gptr();
        }
    //Zone end ---
    
    private:
        std::vector<T> &_value;
    };
    
    
    int main() {
        vector<int>  arr{'a',12,13,14,15};
        cout<<"The array: ";
        for (int i=0; i<arr.size(); i++)
            cout<<arr[i]<<" ";
        cout<<endl;
        cout<<"  storage: ";
        for (int i=0; i<arr.size()*sizeof(int); i++) {
            char *ptr = (char*)arr.data();
            cout<<static_cast<int>(ptr[i])<<" ";
        }
        cout<<endl;
    
        vectorbuf<int> vbuf(arr);
        std::istream is(&vbuf);
        arr.push_back(16); //!!! wrong code here !!!
    
        //the size of arr is 6*4 == 24, with sizeof(int)==4
        for (int i=0; i<26; i++) {
            cout<<"good?"<<is.good()
                <<", fail?"<<is.fail()
                <<", bad?"<<is.bad()
                <<", eof?"<<is.eof()
                <<", tellg="<<is.tellg();
    
            //Note there must be char
            //'int a' would not accepted and make is.fail() to true
            //and std::noskipws is also importanted
            char a;
            is>>std::noskipws>>a;
            int out = a;
            cout<<", Read from arr: "<<out<<endl;
        }
        return 0;
    }
    

    【讨论】:

      【解决方案4】:

      您可以通过将数据分配给std::string 并使用绑定到该数据的std::istringstream 来做到这一点(抛开unsigned charsigned char 的转换问题):

      std::string s((char*)networkMessage.data(),networkMessage.size());
      std::istringstream iss(s);
      
      std::istream& stream = iss;
               // ^ Note the reference here.
      
      processData(stream);
      stream.close();
      

      【讨论】:

      • 由于std::istreamstream 派生自std::istream,您可以将iss 直接传递给processData() 而无需声明中间变量:processData(iss); 此外,此解决方案的缺点是必须复制std::vector 数据先到std::string,因为processData() 无法直接从std::vector 读取。
      • @Remy 为了清楚起见,我确实设计了这样的示例。
      • 这可能是为了清晰,但肯定不是为了效率/性能,尤其是对于大数据。
      • 是的,这会将数据复制到string。不太可能是你想要的。
      • 这里有两份。
      【解决方案5】:

      这适用于任何类型的向量,而不仅仅是uint8_t

      标准

      template <class T>
      auto make_istringstream_std_1(const std::vector<T>& v) -> std::istringstream
      {
          using namespace std::string_literals;
          std::string str;
      
          for (auto& e : v)
          {
              str += std::to_string(e) + " "s;
          }
          // the trailing space is not an issue
      
          return std::istringstream{str};
      }
      

      标准算法

      template <class T>
      auto make_istringstream_std_2(const std::vector<T>& v) -> std::istringstream
      {
          std::stringstream ss;
          std::copy(v.begin(), v.end(), std::ostream_iterator<int>(ss, " "));
          // the trailing space is not an issue
      
          return std::istringstream{ss.str()};
      }
      

      提升

      template <class T>
      auto make_istringstream_boost(const std::vector<T>& v) -> std::istringstream
      {
          using boost::adaptors::transformed;
          using boost::algorithm::join;
      
          return std::istringstream{
              join(v | transformed([](int a) { return std::to_string(a); }), " ")};
      }
      

      归因:

      How to transform a vector<int> into a string?

      A good example for boost::algorithm::join

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-26
      • 1970-01-01
      • 1970-01-01
      • 2017-10-29
      • 2011-12-07
      • 2020-11-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多