Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Program Objects: Reusing and Redirecting C++ Programs

0.00/5 (No votes)
6 Jan 2005 2  
By writing programs as objects, it can be easy to reuse programs, and redirect them to one another.

Introduction

I remember, as a young programmer at university, how impressed I was with the productivity of the UNIX gurus. They would write sophisticated software in just one or two lines of code using strangely named tools like bash and awk. It took a while until I realized that the secret to their productivity was program reuse. Commonly, UNIX programmers wrote programs in a way that made them easily reused, where the primary input was the standard in, and the primary output was the standard out. This kind of program is often called a UNIX filter. The UNIX shells such as bash, and tools like awk, provided various ways for filters to be chained together and piped to and from files.

It has always bothered me that there was no easy way to achieve the same kind of effect from within C++ other than communicating directly with the OS. It seems to make perfect sense that if I have the source code to two C++ programs which behave like UNIX filters, redirecting the output from one to the other should be written as a one liner:

  MyProgram1 > MyProgram2;

Using Program Objects

In order to write programs so that they can be reused in other programs, you can write the program as a class derived from the Program class, i.e.:

  // helloworld.hpp


  #include <iostream>

  #include "programs.hpp"


  class HelloWorldProgram : public Program {
  protected:
    virtual void Main() {
      cout << "Hello world!\n" << endl;
    }
  };

The corresponding .cpp file would contain only the following:

  // hello_world.cpp


  #include "hello_world.hpp"


  int main() {
    HelloWorldProgram().Run();
    return 0;
  }

This might not look like much at first glance, but now your program can be used within another program as if it were a UNIX filter. Consider the following programs:

  // upper_case.hpp


  #include <iostream.hpp>

  #include <cctype.hpp>

  #include "programs.hpp"


  class UpperCaseProgram : public Program {
  protected:
    virtual void Main() {
      char c;
      while (cin.get(c)) cout.put(toupper(c));
    }
  };

Now we can combine them:

  // upper_case_hello_world.hpp


  #include "programs.hpp"

  #include "upper_case.hpp"

  #include "hello_world.hpp"


  class UpperCaseHelloWorldProgram : public Program {
  protected:
    virtual void Main() {
      HelloWorldProgram() > UpperCaseProgram();
    }
  };

Notice that the Program class overloads the greater-than operator (>).

Redirecting to and from Stream

Program objects can also be redirected to and from streams, i.e.:

  stringstream s;
  HelloWorldProgram() > s;
  s > UpperCaseProgram();

Programs and streams can also be chained together in arbitrarily long sequences:

  fstream f("c:\\tmp.txt");
  stringstream s;
  HelloWorldProgram() > f > UpperCaseProgram() > s;

The only caveat is that one stream can't be redirected to another stream.

The Program Class

Here is the code for the Program class:

  class Program {
  public:
    Program() {
      old_in = cin.rdbuf();
      old_out = cout.rdbuf();
      old_err = cerr.rdbuf();
      old_log = clog.rdbuf();
    }
    ~Program() {
      ResetStreams();
    }
    void Run() {
      Main();
      ResetStreams();
    }
    void SetStdIn(istream& in) {
      cin.rdbuf(in.rdbuf());
    }
    void SetStdOut(ostream& out) {
      cout.rdbuf(out.rdbuf());
    }
    void SetStdErr(ostream& err) {
      cerr.rdbuf(err.rdbuf());
    }
    void SetStdLog(ostream& log) {
      clog.rdbuf(log.rdbuf());
    }
    void ResetStreams() {
      cin.rdbuf(old_in);
      cout.rdbuf(old_out);
      cerr.rdbuf(old_err);
      clog.rdbuf(old_log);
    }
  protected:
    virtual void Main() = 0;
  private:
    streambuf* old_in;
    streambuf* old_out;
    streambuf* old_err;
    streambuf* old_log;
  };

The Redirection Operator

The greater-than operator (>) is used as the redirection operator. The following overloads are provided:

  PipeChain operator>(Program& in, Program& out) {
    return PipeChain(in, out);
  }

  PipeChain operator>(iostream& in, Program& out) {
    return PipeChain(in, out);
  }

  PipeChain operator>(Program& in, iostream& out) {
    return PipeChain(in, out);
  }

  PipeChain& operator>(PipeChain& in, Program& out) {
    in.ChainTo(out);
    return in;
  }

  PipeChain& operator>(PipeChain& in, iostream& out) {
    in.ChainTo(out);
    return in;
  }

The only really tricky part of the library is how PipeChain works. A PipeChain is constructed by the first redirection operator in a chain. In other words, for the example a > b > c;, a > b returns a PipeChain, which is then passed by reference to PipeChain& > c;. This allows us to construct arbitrarily long PipeChains. Only when the chain is destroyed are the programs are redirected and then run.

Anyway, the only thing you really need to know about PipeChain is that when you pipe from a program to another, the programs are run sequentially. The first program stores its output in a stringstream, which is then fed as the input to the second program only after the first program is finished running. The reason for this approach is that it is portable to even single threaded environments.

Summary

When writing a program to be reused in the manner described, always keep in mind that the standard input and standard output might be redirected. Also, use only C++ streams and not C-style I/O functions. This library is very exciting and useful to me, because I am continually writing small test and demonstration programs for the various libraries I write. Hopefully, it will be helpful for you as well.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here