c++ cpp使用istream_iterator填充向量,避免复制

xtfmy6hx  于 2022-12-05  发布在  其他
关注(0)|答案(1)|浏览(100)

这个问题看起来和这里的其他问题很相似,但是一些关键的细节在这个例子中是不同的(他们总是把迭代器的元素复制到向量中,就我所看到的帖子而言)。
坚持一个非常常见的习惯用法(据我所知),我使用file-〉ifstream-〉istream_iterator-〉vector(这种方法在向量的类型上调用>>)。
取消引用仅返回最近读取的对象的副本
在我的例子中,我想读取一些包含向量成员的对象,这意味着当前读取的对象被复制以插入到向量中(这意味着复制构造整个向量成员),并且在阅读新对象时立即构造全新的对象。
所以这个副本是不需要的,只是拖慢了整个过程。有没有办法消除这个不需要的副本(我想,istream_iterator可能只是move把对象取出,然后构造一个新的对象,但也许有另一种方法可以避免这种复制)?
有关该问题的说明,请参见一些示例代码:

#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>

struct My_class {
  std::vector<std::string> vec;

  // default/copy/move constructors to illustrate what constructors are being invoked
  My_class() : vec{} {
      std::cout << " default" << std::endl;
  }
  My_class(My_class const &other) : vec{other.vec} {
      std::cout << " copy" << std::endl;
  }
  My_class(My_class &&other) noexcept : vec{std::move(other.vec)} {
      std::cout << " move" << std::endl;
  }

  friend std::istream& operator>>(std::istream& is, my_class& i) {
    i.vec.clear(); // needed since i still contains the data from the previous object read
    std::string l;
    // in other use cases some more lines would be read or maybe the lines get preprocessed (e.g. split it up) and then inserted into the vector
    std::getline(is, l);
    i.vec.push_back(l);
    return is;
  }
};

int main(int argc, char* argv[]) {
  std::ifstream ifs{argv[1]};
  std::vector<my_class> data{std::istream_iterator<my_class>{ifs}, {}};

  for(const auto& _ele : data){
      for(const auto& ele : _ele.vec){
          std::cout << ele << " ";
      }
      std::cout << std::endl;
  }
}

第二次尝试:

#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>
#include <memory>
#include <ranges>

static int cpy_cnt = 0;

struct My_class {
    std::vector<std::string> vec;

  // default constructor
  My_class() : vec{} {
    std::cout << vec.size() << " default" << std::endl;
  }

  My_class(My_class&&)      = default;
  My_class(const My_class&) = default;

  My_class& operator=(My_class&&) = default;
  My_class& operator=(My_class&)  = default;

  friend std::istream& operator>>(std::istream& is, My_class& i) {
    i.vec.clear();
    std::string l;
    std::getline(is, l);
    i.vec.push_back(l);
    return is;
  }
};

int main(int argc, char* argv[]) {
  std::ifstream ifs{argv[1]};
  std::cout << "iter create" << std::endl;
  auto tmp = std::views::istream<My_class>(ifs);
  std::cout << "vec create" << std::endl;
  std::vector<My_class> data2{};
}

给出包含error: no matching function for call to '__begin'的错误(不会粘贴完整的长错误消息clang)。
我一直在摆弄gccclang,它的接缝,错误只发生在clang,而与gcc一切都很好。
使用cd build && cmake -DCMAKE_BUILD_TYPE=Debug -D CMAKE_C_COMPILER=clang -D CMAKE_CXX_COMPILER=clang++ -DCMAKE_EXPORT_COMPILE_COMMANDS=1 ..cd build && cmake -DCMAKE_BUILD_TYPE=Debug -D CMAKE_C_COMPILER=gcc -D CMAKE_CXX_COMPILER=g++ -DCMAKE_EXPORT_COMPILE_COMMANDS=1 ..进行配置

$ g++ --version
g++ (GCC) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ clang++ --version
clang version 14.0.6
Target: x86_64-pc-linux-gnu
Thread model: posix
ttygqcqt

ttygqcqt1#

正如您所发现的,引用istream_iterator将为您提供一个副本。一个解决方法是使用views::istreamranges::move

std::ifstream ifs{argv[1]};
auto ifs_view = std::views::istream<My_class>(ifs);

std::vector<My_class> data;
std::ranges::move(ifs_view, std::back_inserter(data));

根据您的用例,您也可以直接在ifs_view上循环以避免任何构造(因为您的operator >>直接将数据插入到底层向量中):

for(const auto& _ele : ifs_view) { 
   // for loop logics here...
}

旁注,istream_view要求对象是可赋值的,而您的My_class的赋值操作符现在被隐式删除了,所以您需要自己至少提供一个,或者只是默认复制/移动构造函数。

相关问题