Dev Notes

Software Development Resources by David Egan.

Construct C++ Objects From Stdin


C++
David Egan

Objects From Stdin: C++

This project is an example of how stdin data can be used to construct objects in C++ under Linux.

Data is input via stdin either through the terminal or by means of file redirection.

You could use this as boilerplate for importing data stored in a file into C++ data structures. In this case, data is imported on a line-by-line basis, with each line providing data for a separate object.

A vector of unique_ptr to objects is used to store objects.

Use Case

I put this project together to better understand input streams in C++. The aim was to accept input by means of file redirection or terminal input. I also wanted to switch to terminal input for additional interaction in the event of file redirection input of the main body of data.

One interesting result of this approach is that you can input data either by means of file redirection or by means of terminal input in the default stdin way.

File Redirection

Redirection in Linux involves changing standard input/output devices.

Usually, the standard input device stdin is the keyboard and the standard output device stdout is the screen.

This means that in C++ std::cin >> input; accepts input from the keyboard, and std::cout << output outputs data to the screen.

To input data from a file, redirection can be used:

./myProg < input.txt

In this case, the shell streams in the contents of input.txt to stdin instead of accepting keyboard input from the terminal.

Terminal Input

If you run the binary file without supplying data by means of file redirection, the user is required to supply data at the terminal. Data is entered:

  • Line-by-line, with each line representing a separate object (\n delineates objects).
  • Individual scores are separated by spaces.
  • Input is ended by entering Ctrl-D which is the Linux end-of-file signal.

Switching from File Redirection to Terminal

When input is redirected from a file, std::cin refers to the redirected file. If you need to get input from the terminal at a later stage in the programme, you can assign another input buffer using std::cin.rdbuf().

// Define an input stream as the terminal for the current process (/dev/tty)
std::fstream in("/dev/tty");

// Set the streambuf of std::cin as the streambuf of /dev/tty.
std::cin.rdbuf(in.rdbuf());

// stdin is now /dev/tty - keyboard input.

For a working example, see below:

#include <ostream>
#include "record.h" // Defines Record, a custom data type (class)

int main()
{
	// Create a vector of *Record unique pointers
	std::vector<std::unique_ptr<Record>> records;
	while (1) {
		// Make a unique pointer to a Record
		auto s = std::make_unique<Record>();

		// Input data to this object - reset object if EOF is indicated
		if (s->inputFromStdin() == -1) {
			s.reset();
			break;
		}

		// Move the object to the array
		records.push_back(std::move(s));
	}
	
	// Reset stdin to terminal	
	std::fstream in("/dev/tty");
	std::cin.rdbuf(in.rdbuf());
     
	for (auto& record : records) {
		std::cout << *record;
	}
	
	try {
		int index;

		// Output to tty
		std::streambuf *backup = std::cout.rdbuf();
		std::ofstream out("/dev/tty");
		std::cout.rdbuf(out.rdbuf());
		
		// This should print to screen even if output is redirected to file.
		std::cout << "Enter index: ";
		std::cin >> index;
		
		std::cout.rdbuf(backup); // Reset the original streambuf to std::cout
		Record s = *(records[0]);
		std::cout << "value: " << s[index] << '\n';
	} catch (const IndexOutOfBoundsException& e) {
		std::cout << "ERROR: index out of bounds." << '\n';
	}
}

GitHub Repo.

std::fstream is the input/output file stream class. The code above associates the /dev/tty (terminal device) stream with the variable in.

The next line sets the std::streambuf for std::cin to the std::streambuf of in which has been set to dev/tty.

The function std::ios::rdbuf() is used to get and set a stream buffer:

streambuf* rdbuf() const; // get - returns a pointer to the stream buffer of the object associated with stream
streambuf* rdbuf (streambuf* sb); // sets the object pointed at by sb, clears error flags.

Headers: <ios> <iostream>.

Streams & Stream Buffers

A stream is a communication channel between a programme and it’s environment.

Streams involve data flowing from a source to a destination - for example, data may flow from the programme to the screen, or may be read from the user’s keyboard into the programme.

A stream buffer is a memory buffer for the stream - the streambuf is an interface that defines a mapping from a stream to a device, file or memory.

  • The stream is responsible for converting data into an appropriate format.
  • The streambuf actually communicates the data.

References


comments powered by Disqus