Home > front end >  How to wrapper ofstream?
How to wrapper ofstream?

Time:07-30

I'm trying to wrapper the standard ofstream, and hope I can do additional compare and output.

Everything goes well, except the std::endl or end used as first output token.

But, when I turn on the #if0 ... #endif block in the main function, the g report many errors.

How can I solve it? Is there a alternative solution.

compile command is: g test.cpp -std=c 11 -ggdb -o a.out

below is the code

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

class Logger {
 private:
  template <typename T>
  friend ofstream& operator<<(Logger&, T);

  ofstream os_;
  std::string file_name;
  uint32_t count = 0;

 public:
  // Logger(std::string name) : file_name(name), curIndentLevel_(0)
  explicit Logger(const std::string& name) : file_name(name) {
    // open the file and ready to write.
    os_.open(file_name, std::ofstream::out | std::ofstream::app);
  }
};

template <typename T>
inline ofstream& operator<<(Logger& log, T op) {
  // write stream to the target file.

  if ((log.count % 100) == 0) {
    // output a  split token
    log.os_ << "=============" << std::endl;
  }

  log.os_ << ' ';
  log.os_ << op;
  log.os_.flush();
  log.count  ;

  return log.os_;
}

class MyClass {
 public:
  uint32_t value = 0xf;
  uint32_t x = 0;
  std::string file_name = "456.txt";

  Logger log = Logger("456.txt");

 public:
  MyClass() {}
  ~MyClass() {}
};

int main() {
  Logger log("123.txt");

  for (uint32_t i = 0; i < 5000; i  ) {
    log << "Hello World!" << std::hex << " 0x= " << 100 << ", sdfdsfs"
        << " ABC " << std::dec << " =0x " << 100 << std::endl
        << endl;
  }

#if 0
  // why did the "end" can not be the first element?
  // how to solve it.
  log <<endl;      //errors.
  log <<std::endl;  //errors
  log <<std::endl<< "***** The log execute  successfully."<<std::endl<<std::endl; //errors
#endif

  log << std::dec;
  log << std::hex;

  log << "Hello World!" << std::endl;
  return 0;
}


CodePudding user response:

Just use inheritance directly.

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

class Logger : public ofstream {
template<typename T> friend Logger& operator<<(Logger&, T);
public:
    Logger(const string& file_name) {
        open(file_name);
    }
private:
    uint32_t count = 0;
};

template<typename T>
inline Logger& operator<<(Logger& log, T op) {
    //write stream to the target file.
    auto& base_log = static_cast<ofstream&>(log);
    if( (log.count0) == 0){
        //output a  split token
        base_log << "============="<<std::endl;
    }
    base_log << ' ';
    base_log << op;
    base_log.flush();

    log.count  ;
    return log;
}

int main() {

    Logger log("123.txt");

    for(uint32_t i =0;i<5000;i  ){
        log << "Hello World!" <<std::hex<<" 0x= "<<100 <<", sdfdsfs" << " ABC "<<std::dec << " =0x " << 100<<std::endl<<endl; 
    }

    log <<endl;
    log <<std::dec; 
    log <<std::hex; 

    log << "Hello World!" << std::endl;
    return 0;
}

CodePudding user response:

std::endl is a template function with the following declaration:

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );

Let's see how std::basic_ostream's operator<< is overloaded to take such a template function:

basic_ostream& operator<<(
    std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&) );

You need an overload of operator<< with that signature. For example (as a member function):

using ostream_type = std::basic_ostream<ofstream::char_type, ofstream::traits_type>;

Logger& operator<<(ostream_type& (*func)(ostream_type&));

CodePudding user response:

First of all, the reason it doesn't work when you pass std::endl as the first thing you insert to the stream (but does as the second or subsequent items) is that your operator<< returns the underlying stream object, so all but the first item are inserted directly to the underlying stream, not to the Logger object.

Since what you apparently want is to insert a separator line every 100 lines of output, I think I'd do things somewhat differently: I'd create a custom stream buffer.

#include <thread>
#include <sstream>
#include <streambuf>
#include <iostream>
#include <string>
#include <functional>

template <class T=char>
class separator: public std::basic_streambuf<T> {
public:
    using int_type = typename std::basic_streambuf<T>::int_type;
    using char_type = typename std::basic_streambuf<T>::char_type;
    using traits_type = typename std::basic_streambuf<T>::traits_type;

    separator(std::basic_streambuf<T> *dest) : sink(dest) {}

    ~separator() { overflow(traits_type::eof()); }
protected:

    virtual int_type sync() override { 
        if (nullptr == sink)
            return 1;

        return sink->pubsync();
    }

    virtual int_type overflow(int_type c) override {
        if (sink == nullptr)
            return 1;

        if (traits_type::eq_int_type(traits_type::eof(), c)) {
            sink->pubsync();
            return traits_type::not_eof(c);
        }

        if (c == '\n') // if we're going to write a new-line, count the line.
              count;

        auto ret = sink->sputc(c); // write the character

        if (count == sep_count) {   // if we need to, write out the separator
            sink->sputn(sep, sizeof(sep)-1);
            count = 0;
        }

        return ret;
    }

    int count = 0;

    // adjusted to 10 for demo purposes. Should be 100:
    const int sep_count = 10;
    char sep[15] = "=============\n";
    std::basic_streambuf<T> *sink = nullptr;
};

template <class T = char>
class basic_separator_stream : public std::basic_ostream<T> {
    separator<T> buf;
public:
    basic_separator_stream(std::basic_ostream<T> &file)
        : buf(file.rdbuf())
    {
        std::basic_ostream<T>::rdbuf(&buf);
    }
};

using Logger = basic_separator_stream<char>;
using wLogger = basic_separator_stream<wchar_t>;

int main() {

    Logger out(std::cout);

    out << std::endl;   // endl works here

    // write out enough lines we should see a couple separators
    for (int i=0; i<20; i  ) {
        out << "Log message " << i << std::endl;    // endl works here too
    }
}

This produces output like this:


Log message 0
Log message 1
Log message 2
Log message 3
Log message 4
Log message 5
Log message 6
Log message 7
Log message 8
=============
Log message 9
Log message 10
Log message 11
Log message 12
Log message 13
Log message 14
Log message 15
Log message 16
Log message 17
Log message 18
=============
Log message 19

So, at least as I read the intent, pretty much what we want (other than my having changed it to a separator every 10 lines instead of 100). I may have misunderstood the situation though--maybe you wanted to insert a separator every 100 items instead of every 100 lines?

  • Related