cis3990-25fa (Fall 2025) Home Schedule Assignments Tools & Refs HW 04: File Readers

Implementing C++ classes that abstract away POSIX file I/O.

Goals

Collaboration

For assignments in CIS 3990, you will complete each of them on your own. However, you may discuss high-level ideas with other students, but any viewing, sharing, copying, or dictating of code is forbidden. If you are worried about whether something violates academic integrity, please post on Ed or contact the instructor(s).

Setup

You can downlowd the starter files into your environment by running the following command. You should run it from inside your git repository since that is how you will submit your code.

curl -o readers_starter.zip https://www.seas.upenn.edu/~tqmcgaha/cis3990/current/projects/code/readers_starter.zip

You can also download the files manually here if you would like: readers_starter.zip

From here, you need to extract the files by running

unzip readers_starter.zip

You should now have a folder called readers that has the starter files. We expect this to be a folder inside the base of your github repository, similar to cowsay and check-setup. If you accidentally put the folder somewhere else, you can move it with the mv command in the terminal.

Overview

A commonly utilized feature in programming is file I/O (Input/Output). In this homework assignment, you will be implementing two file readers that have similarities with other higher level file I/O implementations. You will start with a simpler file reader, and the move on to create a file reader that implements more features and utilizes caching to aid performance.

Each file reader will be their own class, meaning we will have to define the constructor, destructor, and methods for our implementation.

Note that our user-defined programs don’t have direct access to files and disk. These are protected by the operating system. As a result, both of our readers will make use of system calls through POSIX (Portable Operating System Interface X) to perform the file I/O. You will likely find the following POSIX functions necessary for your implementation:

For each of the functions, note that we provided a link to some documentation. You may also want to see how we used these functions as examples in lecture slides. You can also access that documentation through the terminal by typing in the link text as a command (e.g. typing in man 2 open in the terminal).

SimpleFileReader

This is the first file reader that you will implement as part of the homework. SimpleFileReader, as its name suggests, is a simple wrapper around the POSIX file I/O implementation. One can open a file, close a file, read one or more characters, and a few other simple features. Most of the work done in these functions will be handled largely by POSIX, but it is up to you though to figure out how to do this. See the Instructions section below for details on the code files

There are also further notes about this reader in SimpleFileReader.h. We recommend that you read through this file before you start your implementation.

BufferedFileReader

This is the second (and last) file reader you will implement for this homework. You will notice some similarities between BufferedFileReader and SimpleFileReader, but there is some added complexity.

BufferedFileReader implements a caching strategy. To understand this strategy, consider the following example:

BufferedFileReader bf (some_file_name);
char c = bf.GetChar();

If we had used SimpleFileReader in this case, GetChar() would simply call read() requesting one character from the file. Instead, BufferedFileReader maintains an internal buffer (char array) of size 1024, and will use this array to minimize calls to POSIX read(). In the code example above, when BufferedFileReader calls GetChar(), it will not just read 1 character, instead it will call read() and try to fill in the entire buffer of 1024 characters. While this is more work initially, this means that on subsequent calls to GetChar(), BufferedFileReader can simply return the next char in its internal buffer, rather than having to call POSIX read(). Once you have reached the end of the buffer and need to process more characters, the BufferedFileReader should attempt to repopulate the buffer with the next 1024 characters of the file. Other functions for reading like operator>> and GetLine will also make use of the buffer. To maintain the buffer requires multiple fields, which are described in BufferedFileReader.hpp. We recommend that you read through this file before you start your implementation.

Instructions

Among these starter files you will find:

We recommend reading all of these files before your start, especially the .hpp files listed. Note: you are only allowed to modify the following files SimpleFileReader.cpp, BufferedFileReader.cpp, and BufferedFileReader.hpp. This means that your code should work with the other files as they are when initially supplied. Note that you are allowed to modify BufferedFileReader.hpp but it still must work with the tests. This means that any modifications you may make would likely be for adding new private data members or private helper methods. We suggest that you do come up with a private helper method to use for this object. Additionally, you can modify the test files if you like (for debug purposes), but we will be testing your readers against un-modified versions of the files.

Hints

Here are a few hints and tips that you may find useful when approaching this homework.

  1. Don’t wait until the end to test. You can create “empty” definitions for functions, just enough to compile, so that you can test your other functions.
  2. There are many functions in cctype that may be of use to you e.g. isspace().
  3. Legal File descriptors will be non-negative (positive or zero), so you can use a negative number to represent an invalid file descriptor.
  4. It would be a good idea to create one or more helper functions to aid in your implementation of BufferedFileReader.
  5. You can add new data members (fields) to BufferedFileReader.hpp if that would be useful. However, you MUST make use of the data member buffer.
  6. EOF is a literal value that represents the end of file as a character. Example: char end = EOF;
  7. The logic for when a file reaches the end of file and is no longer “good” can be a bit hard to understand. The way to think about this is that the file reader is no longer good once it tries to extract information and then it fails. e.g. if there was a file only containing HELLO, then doig bf >> str to read in “HELLO” would leave the reader still “good”. Only on some operation (e.g. Getline(bf, line), bf >> value, or bf.GetChar()) that would be called after and it fails (cause there is nothing left to read) would the file reader no longer be “good”.

Grading & Testing

Compilation

We have supplied you with a Makefile that can be used for compiling your code into an executable. To do this, open the terminal and then type in make.

You may need to resolve any compiler warnings and compiler errors that show up. Once all compiler errors have been resolved, if you ls in the terminal, you should be able to see an executable called test_suite. You can then run this by typing in ./test_suite to see the evaluation of your code.

Note that your submission will be partially evaluated on the number of compiler warnings. You should eliminate ALL compiler warnings in your code.

Valgrind

We will also test your submission on whether there are any memory errors or memory leaks. We will be using valgrind to do this. To do this, you should try running: valgrind --leak-check=full ./test_suite

If everything is correct, you should see the following towards the bottom of the output:

 ==1620== All heap blocks were freed -- no leaks are possible
 ==1620==
 ==1620== For counts of detected and suppressed errors, rerun with: -v
 ==1620== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

If you do not see something similar to the above in your output, valgrind will have printed out details about where the errors and memory leaks occurred.

catch2

As with mentioned above, you can compile the your implementation by using the make command. This will result in several output files, including an executable called test_suite.

After compiling your solution with make, You can run all of the tests for the homwork by invoking:

$ ./test_suite

You can also run only specific tests by passing command line arguments into test_suite

For example, to only run the “SimpleFileReader” test, you can type in:

$ ./test_suite [Test_SimpleFileReader]

You can specify which tests are run for any of the tests in the assingment. You just need to know the names of the tests, and you can do this by running:

$ ./test_suite --list-tests

These settings can be helpful for debugging specific parts of the assignment, especially since test_suite can be run with these settings through valgrind and gdb!

Code Quality

For this assignment, we will be checking the quality of your code in two ways:

Manual Checks

For this assignment the only checks we will be doing are:

Clang Format and Clang Tidy

The makefile we provided with this assignment is configured to help make sure your code is properly formatted and follows good (modern) C++ coding conventions.

To do this, we make use of two tools: clang-format and clang-tidy

To make sure your code identation is nice, we have clang-format. All you need to do to use this utility, is to run make format, which will run the tool and indent your code propely. Code that is turned in is expected to follow the specified style, code that is ill-formated must be fixed and re-submitted.

clang-tidy is a more complicated tool. Part of it is checking for style and readability issues but that is not all it does. It also checks for and can detect possible bugs in your program.

Because of all this, we are enforcing that your code does not produce any clang-tidy errors. You can run clang-tidy on your code by running: make tidy-check. Note that you will have to fix any compiler errors before clang-tidy will run (and be useful).

If you have any questions about understanding an error, please ask on Ed discussion and we will happily assist you.

Submission

Please submit your completed SimpleFileReader.cpp, BufferedFileReader.cpp, BufferedFileReader.hpp and other files to Gradescope via your github repo. We will be checking for compilation warnings, valgrind errors, making sure the test_suite runs correctly, and that you did not violate any rules established in the specification.