Click here to Skip to main content
15,884,986 members
Articles / Programming Languages / C++
Article

Program Objects: Reusing and Redirecting C++ Programs

Rate me:
Please Sign up or sign in to vote.
4.86/5 (12 votes)
6 Jan 20053 min read 47.9K   416   29   7
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


Written By
Software Developer Ara 3D
Canada Canada
I am the designer of the Plato programming language and I am the founder of Ara 3D. I can be reached via email at cdiggins@gmail.com

Comments and Discussions

 
Generalproblems compiling Pin
thides17-Jan-05 2:43
thides17-Jan-05 2:43 
Generalcat filename.pts| recon Pin
Member 163765411-Jan-05 3:04
Member 163765411-Jan-05 3:04 
GeneralRe: cat filename.pts| recon Pin
Edwin G. Castro12-Jan-05 8:52
Edwin G. Castro12-Jan-05 8:52 
GeneralConfusion about redirection and piping. Pin
Christopher Diggins7-Jan-05 7:26
professionalChristopher Diggins7-Jan-05 7:26 
GeneralDon't worry Pin
Arno Nym7-Jan-05 12:33
Arno Nym7-Jan-05 12:33 
GeneralRe: Don't worry Pin
Dante Shamest7-Jan-05 14:35
Dante Shamest7-Jan-05 14:35 
GeneralRe: Don't worry Pin
Hofver10-Jan-05 22:47
Hofver10-Jan-05 22:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.