Home > other >  How can I couple the lifetimes of two variables?
How can I couple the lifetimes of two variables?

Time:08-09

The following code compiles and runs fine, but does not work as intended (compare the comments in the code). I believe the reason is that the lifetime of is ends with getFileIter, so the stream buffer iterator has nothing left to iterate over. (By contrast, I think I remember that in C#, is would live as long as the stream buffer iterator points to, but that does not seem to be the case in C .)

One fix to avoid this is using ... *is = new std::ifstream(p), but this means I have to take care of the pointer myself when I'm done.

Is there any mechanism to link the lifetimes of is and its stream buffer iterator?

// g   test.cpp -o test && ./test

#include <iostream>
#include <fstream>

std::istreambuf_iterator<char> getFileIter(std::string p)
{
  std::ifstream is(p);
  //std::ifstream *is = new std::ifstream(p);
  std::cout << "Read " << p << "? " << is.good() << std::endl;
  return std::istreambuf_iterator<char>(is.rdbuf());
}

int main()
{
  std::string p1("/etc/passwd");
  std::string p2("/etc/group");

  // Prints 1 as if the two files were identical
  std::cout << std::equal(
    getFileIter(p1),
    std::istreambuf_iterator<char>(),
    getFileIter(p2)
  ) << std::endl;

  // Prints nothing except /////
  auto x = getFileIter(p1);
  int i = 0;
  do
  {
    std::cout << *(x  ) << "/";
    i  ;
  } while (i < 5);
  std::cout << std::endl;

  return 0;
}

CodePudding user response:

As one comment suggests, combine both things: stream and its iterator in a struct - to ensure the iterator will not outlife the stream:

struct FileIter
{
    FileIter(const std::string& p) 
        : is(p), iter(is.rdbuf())
    {}
    std::ifstream is;
    std::istreambuf_iterator<char> iter;
};

  std::cout << std::equal(
    FileIter(p1).iter,
    std::istreambuf_iterator<char>(),
    FileIter(p2).iter
  ) << std::endl;

  FileIter x(p1);
  int i = 0;
  do
  {
    std::cout << *(x.iter  ) << "/";
    i  ;
  } while (i < 5);
  std::cout << std::endl;

If you like -- you might make this FileIter looks like iterator - see this post

Two important things:

In this code:

  std::cout << std::equal(
    FileIter(p1).iter,
    std::istreambuf_iterator<char>(),
    FileIter(p2).iter
  ) << std::endl;

FileIter might be temporary (nameless) because its lifetime is guaranteed to be as long as to the end of expression (the semicolon).

Here, you cannot replace FileIter x(p1); with auto x = FileIter(p1).iter; because stream in FileIter will end its lifetime with the end of expression (semicolon here).

  FileIter x(p1);
  int i = 0;
  do
  {
    std::cout << *(x.iter  ) << "/";
    i  ;
  } while (i < 5);
  std::cout << std::endl;

  • Related