This assignment is worth 200 points total.
Please read this assignment carefully and follow the instructions EXACTLY.
Do NOT create any additional subdirectories. Work inside the existing
part*/
subdirectories.
Note that certain part*/
directories contain symlinks
(“symbolic links”) to files from previous parts; this simply indicates
that the file is the same as before, and that there’s no need to modify
it further.
Ensure the Makefile for each part builds all executables when you run
make
.
Please refer to the lab submission instruction for other requirements.
Please check your code with valgrind
. You will be penalized if you
have any memory leaks or errors.
Take a look in the part0/
directory at the provided C programs
mdb-add.c
and mdb-cat.c
. Feel free to build them and test them; make
sure you understand what they do and how they work. These are available
as a reference for subsequent parts.
There’s nothing to submit for this part.
$ ./mdb-add mdb
name please (will truncate to 15 chars): new
msg please (will truncate to 23 chars): msg
1: {new} said {msg}
$ ./mdb-add mdb
name please (will truncate to 15 chars): cool
msg please (will truncate to 23 chars): msg
2: {cool} said {msg}
$ ./mdb-add mdb
name please (will truncate to 15 chars): jae woo
msg please (will truncate to 23 chars): mii
3: {jae woo} said {mii}
$ ./mdb-cat mdb
1: {new} said {msg}
2: {cool} said {msg}
3: {jae woo} said {mii}
Work from the part1/
directory for this part.
The skeleton code contains completed mdb-add.cpp
and mdb-cat.cpp
programs which make use of the uncompleted class Mdb
in mdb.h
.
Implement this class so that mdb-add
and mdb-cat
work correctly,
like their C counterparts from Part 0 (with a few caveats, to be
addressed in Part 2).
Implement class Mdb
’s constructor, destructor, and member functions,
as well as any global operator overloads, per their usage in
mdb-add.cpp
and mdb-cat.cpp
.
mdb.h
additionally declares an operator<<
overload for
struct MdbRec
which must be defined. Formatting should mimic that
of Part 0’s mdb-cat
.Decide whether to let the compiler generate the copy constructor and/or assignment for
class Mdb
, or delete
them; do NOT define your own copy constructor and/or assignment
(they are not used in the example programs).
Create mdb.cpp
to house all definitions, except for the size
and
operator[]
member functions; these should be defined inline in the
class.
Note that mdb-add
will create the database file if it doesn’t exist already.
Keep this in mind when you implement class Mdb
.
A new record added to an Mdb
instance must be
reflected both in-memory and in the associated file.
A user of class Mdb
should not be able to modify any existing
in-memory records (only add new ones).
We will use C file I/O in Part 1 and Part 2. We will switch over to C++ file streams in Part 3.
You may NOT modify the data members of class Mdb
, or add any
additional data members.
The constructor will need to open the database file and read all
records into the private std::vector
. The easiest way is to
std::fopen
with mode "a+b"
for reading and appending, then
std::fseek
to the beginning before reading through the database
file.
The Mdb
only needs to read in the file once; don’t bother
keeping up-to-date with any subsequent changes on the filesystem.
It’s okay if ./mdb-cat noexist_file
creates a new file for this
part; it’s not desired behavior, but this will be remedied in Part
2.
When C standard library functions fail, you should throw the following exception:
std::system_error(errno, std::generic_category(), "some msg")
For your convenience, we provide a macro die
that throws this exception.
You’re welcome to use it if you’d like.
Whenever you feel an urge to use NULL
, consider getting in the
habit of using C++’s nullptr
instead; it’s like NULL
, but it
enables stronger type-checking.
Work from the part2/
directory for this part.
Assuming all went well in Part 1, your class Mdb
implementation
likely works fine for the average database file. But upon closer
inspection, it’s evident that this model is a bit too inflexible;
our Mdb
constructor assumes the specified database file may be used
for both reading and writing, so if we need to open a read-only file
with mdb-cat
, for example, this will fail despite that our intent
is only to read. You may confirm this for yourself with
chmod -w some_mdb ; ./mdb-cat some_mdb
.
To remedy this, you’re going to split Mdb
’s reading and writing
functionality into two separate classes MdbReader
and MdbWriter
,
and have Mdb
inherit from both of these.
MdbReader MdbWriter
\ /
\ /
\ /
\ /
v
Mdb
Afterward, Mdb
should work exactly as it did before, and is used
the same in mdb-add.cpp
. You’ll notice mdb-cat.cpp
has been
tweaked to use an MdbReader
instead of Mdb
, but otherwise
remains unchanged from Part 1. You should now be able to use
mdb-cat
on read-only files, and it should report if a file
doesn’t exist (instead of creating it).
You are only splitting existing functionality, so no members
(except any necessary constructor(s)) should be added to any of
the above classes. Only rearrange what was present in Part 1’s
class Mdb
.
class Mdb
which is already defined for one of its base
classes, if necessary.For each class, decide whether to leave the compiler-generated copy
constructor and/or assignment, or delete
them. Do not delete
the compiler-generated copy constructor and/or assignment if they are safe.
class MdbWriter
’s constructor should take an additional
optional boolean parameter to indicate whether an existing file
should be overwritten or appended to; the default should be to
overwrite.
class Mdb
should only ever append, in accordance with
the Part 1 behavior.class MdbWriter
by itself can be used to create a new database or to
append to an existing one – it should not be storing any records in memory.
We will see an example usage of class MdbWriter
in Part 2b.
Access control should be as narrow as possible. The data members that were
private
from Part 1 should be inaccessible to a user of any class here.
You should not have any redundant code across classes (including member variable declarations).
"+"
in the std::fopen
mode is no longer recommended for
this part, as you don’t need to open the same FILE *
for both
reading and writing; you should be opening the database file
for reading and writing/appending separately.(continuing from last example)
$ ./mdb-cat noexist_file
mdb-cat: noexist_file: fopen: No such file or directory
$ chmod -w mdb
$ ./mdb-cat mdb
1: {new} said {msg}
2: {cool} said {msg}
3: {jae woo} said {mii}
mdb-grep
programCreate a program mdb-grep
which takes a pattern, an input database
file path, and optionally an output database file path as arguments,
performs a search on the input database for the RegEx pattern, and
prints all matching records. If specified, these records should be
written in the order they are found to the output database file.
Implement in mdb-grep.cpp
.
Print records with the same formatting as mdb-cat
.
Must be able to use a read-only file for the input database.
You should catch
any std::exception
and print cleanly, as
done in mdb-add.cpp
/mdb-cat.cpp
.
Should exit with non-zero status if nothing is matched or if an exception is thrown; otherwise exit with zero on success.
mdb-grep
is also built by default, and
removed by the clean
target.std::regex_search
will be helpful.
For handling the optional database file, you may find it simplest
to construct the MdbWriter
from "/dev/null"
if no argument is
provided (a special file for which all written data is discarded).
(continuing from last example)
$ ./mdb-grep
usage: mdb-grep <pattern> <in_database_file> [out_database_file]
$ ./mdb-grep msg mdb
1: {new} said {msg}
2: {cool} said {msg}
$ ./mdb-grep '.*oo' mdb oowoo
2: {cool} said {msg}
3: {jae woo} said {mii}
$ ./mdb-grep '.*oo$' mdb
3: {jae woo} said {mii}
$ ./mdb-grep '.*oo$' oowoo
2: {jae woo} said {mii}
$ ./mdb-cat oowoo
1: {cool} said {msg}
2: {jae woo} said {mii}
$ # The following bash command shows that mdb-grep returns
$ # a non-zero exit code when there are no matching entries.
$ if ! ./mdb-grep noexist oowoo; then echo no match; fi
no match
Study the provided mdb-add-multi.cpp
program. To make it work, you
must implement move construction.
Implement the move constructor(s) for the appropriate base class(es).
Note that the compiler-generated move constructor of a class will call the move constructors of its base classes and its members.
All STL containers have move constructors defined already.
Declare in mdb.h
; define in mdb.cpp
.
Minimal copying of resources should take place.
Update the Makefile so that mdb-add-multi
is also built by default,
and removed by the clean
target.
(continuing from last example)
$ chmod +w mdb
$ ./mdb-add-multi
database filename please (leave empty when done): mdb
database filename please (leave empty when done): mdb2
database filename please (leave empty when done): mdb3
database filename please (leave empty when done):
name please (will truncate to 15 chars): come in come in
msg please (will truncate to 23 chars): calling all mdbs
4: {come in come in} said {calling all mdbs}
1: {come in come in} said {calling all mdbs}
1: {come in come in} said {calling all mdbs}
$ ./mdb-cat mdb mdb2 mdb3
1: {new} said {msg}
2: {cool} said {msg}
3: {jae woo} said {mii}
4: {come in come in} said {calling all mdbs}
1: {come in come in} said {calling all mdbs}
1: {come in come in} said {calling all mdbs}
Work from the part3/
directory for this part.
Copy over your mdb.cpp
and mdb.h
from Part 2. Revise them to
replace all instances of C file I/O (std::fopen
, std::fread
,
std::fwrite
, etc.) with C++’s std::ifstream
and std::ofstream
.
All existing mdb-*
utilities should work as they did before.
There should be no FILE *
anywhere.
Retain proper error-checking; e.g. mdb-cat
should still
report if a file doesn’t exist.
Define operator<<
/operator>>
overloads for an MdbRec
to
std::ofstream
/std::ifstream
, and use these in your updated
MdbWriter
/MdbReader
implementations.
Declare in mdb.h
; define in mdb.cpp
.
Don’t worry about error-checking inside these operators; let the caller be responsible for checking the status of the stream afterward.
Remove any constructor/destructor/operator definitions (or deletions) if the compiler-generated versions are adequate.
Aside from replacing the relevant member variable, you may NOT add additional members to any class.
–
Good luck!
Last updated: 2023-06-05