vendredi 20 février 2015

implement custom input file stream

I try to implement a custom input file stream by creating one buffer stream, derived from std::streambuf, and one ifstream, derived from std::istream.


Here is the code :


<====================================>



namespace test
{
using namespace std;

template<typename T>
string to_str(const T& value)
{
ostringstream oss;
oss << value;
return oss.str();
}


std::ostream& operator << (std::ostream& os, const std::vector<char> &v)
{
for (int i = 0; i < int(v.size()); ++i)
{
if (v[i] == '\n')
os << string("\\n");
else
os << v[i];
}

return os;
}


class streambuf : public std::streambuf
{
private:

const size_t put_back_;

/*!< Internal buffer.*/
vector<char> buffer_;

/*!< File descriptor.*/
FILE *fd_;

/*!< File information.*/
struct stat fileinfo_;

/*!< Position in file.*/
size_t pos_;

/*!< Overrides base class underflow().*/
virtual std::streambuf::int_type underflow();

public:

/*!< Constructor.*/
streambuf(std::size_t buffer_size = 1024, std::size_t put_back = 32);

/*!< Destructor.*/
~streambuf();

/*!< Open.*/
void open(const char *cpath);

/*!< Close.*/
void close();
};

class ifstream : public std::istream
{
private:

/*< Buffer.*/
test::streambuf buffer_;

public:

/*!< Constructors.*/
ifstream();
ifstream(const char *cpath);

/*!< Destructor.*/
~ifstream();

/*!< Open.*/
void open(const char *cpath);

/*!< Close.*/
void close();
};


// Constructor.
streambuf::streambuf(size_t buffer_size, size_t put_back)
: std::streambuf(), put_back_(std::max(put_back, size_t(1))), fd_(NULL),
buffer_(std::max(buffer_size, put_back_) + put_back_), pos_(0)
{
char *end = &buffer_.front() + buffer_.size();
setg(end, end, end);

return;
}


// Destructor.
streambuf::~streambuf()
{
close();

return;
}


std::streambuf::int_type streambuf::underflow()
{
// Buffer not exhausted.
if (gptr() < egptr())
return traits_type::to_int_type(*gptr());

char *base = &buffer_.front();
char *start = base;

// True when this isn't the first fill.
if (eback() == base)
{
// Make arrangements for putback characters.
std::memmove(base, egptr() - put_back_, put_back_);
start += put_back_;
}

CBUG << "buffer = \"" << buffer_ << "\"" << endl;
CBUG << "pos = " << pos_ << endl;
CBUG << "fileinfo.st_size = " << fileinfo_.st_size << endl;

// Start is now the start of the buffer, proper.
size_t count = buffer_.size() - (start - base);
if (count > (fileinfo_.st_size - pos_))
count = fileinfo_.st_size - pos_;

CBUG << "count = " << count << endl;

size_t n(0);

if (count > 0)
if (fd_ != NULL)
n = fread(start, 1, count, fd_);

CBUG << "n = " << n << endl;
CBUG << "buffer = \"" << buffer_ << "\"" << endl;

pos_ += n;

if (n == 0)
return traits_type::eof();

// Set buffer pointers.
setg(base, start, start + n);

return traits_type::to_int_type(*gptr());
}


// Open.
void streambuf::open(const char *cpath)
{
CBUG << "cpath = \"" << cpath << "\"" << endl;

if (fd_ != NULL)
return;

if ((fd_ = fopen(cpath, "r")) == NULL)
throw string("Unable to open local file \"" + to_str(cpath) + "\".");

if (stat(cpath, &fileinfo_) < 0)
throw string("Unable to fstat local file \"" + to_str(cpath) + "\".");

CBUG << "fileinfo.st_size = " << fileinfo_.st_size << endl;
}


// Close.
void streambuf::close()
{
if (fd_ != NULL)
{
fclose(fd_);
fd_ = NULL;
}

CBUG << endl;

pos_ = 0;
bzero(&buffer_.front(), buffer_.size());

char *end = &buffer_.front() + buffer_.size();
setg(end, end, end);
}


// Constructors.
ifstream::ifstream() : std::istream(), buffer_()
{
init(&buffer_);

return;
}

ifstream::ifstream(const char *cpath) : std::istream(), buffer_()
{
init(&buffer_);
open(cpath);

return;
}


// Destructor.
ifstream::~ifstream()
{
close();

return;
}


// Open.
void ifstream::open(const char *cpath)
{
string path(cpath);

CBUG << "path = \"" << path << "\"" << endl;

if (! path.empty())
buffer_.open(path.c_str());
}


// Close.
void ifstream::close()
{
buffer_.close();
}
}


<====================================>


It has nothing specific for the moment because I experience some curious behavior with the following test program :


<================ test.cpp ====================>



#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <exception>
#include <streambuf>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <sys/stat.h>

#define CBUG cout << __FILE__ << ":" <<__FUNCTION__ << ":" << __LINE__ << ":"

namespace test
{
// all previous code here
}

#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
try
{
if (argc == 1)
throw string("Need one argument");

//test::ifstream fin(argv[1]); // to uncomment

{ // test1
test::ifstream fin(argv[1]); // to comment

const int n = 1024;
char *buffer = new char[n];

fin.read(buffer, n);

fin.close();

cout << string(100, '-') << endl;
cout << buffer << endl;
cout << string(100, '-') << endl;

delete[] buffer;
}

cout << string(100, '=') << endl;

{ // test2
test::ifstream fin; // to comment
fin.open(argv[1]);

cout << string(100, '-') << endl;
string buffer;
while(fin >> buffer)
cout << " " << buffer;
cout << endl;
cout << string(100, '-') << endl;

fin.close();
}

cout << string(100, '=') << endl;

{ // test3
test::ifstream fin; // to comment
fin.open(argv[1]);

cout << string(100, '-') << endl;
string line;
while(getline(fin, line))
cout << " " << line;
cout << endl;
cout << string(100, '-') << endl;

fin.close();
}
}
catch (std::exception& err)
{
cout << "C++ exception: " << err.what() << endl;
return 1;
}
catch (std::string& str)
{
cout << str << endl;
return 1;
}
catch (const char* str)
{
cout << str << endl;
return 1;
}
catch(...)
{
cout << "Unknown exception..." <<endl;
return 1;
}

return 0;
}


<============================= test.cpp ==========================>


it should compile : g++ ./test.cpp


if you launch it ./a.out any/path/to/a/file you get what is expected.


Nevertheless, if you uncomment the line "// to uncomment" and comment lines "// to comment", the code within first brackets {...} (test1) works but not the two followers (test2 and test3).


The debug information shows that for test2 and test3, underflow() is never called. I cannot figure out why because the only difference between before and after having uncommented and commented is that fin is known for all tests and never destroyed between.


Could you help me to understand what is going on ?


Thanks in advance for your help


Aucun commentaire:

Enregistrer un commentaire