Book class for PPP2 - Help Needed

Pages: 12
For PPP2, I have to write a class Book. It spans exercises 5 through 9 in Chapter 9 and I've just started Exercise 5. Here are the specifications:

This exercise and the next few require you to design and implement a
Book class, such as you can imagine as part of software for a library. Class Book should have members for the ISBN, title, author, and copyright
date. Also store data on whether or not the book is checked out. Create
functions for returning those data values. Create functions for checking
a book in and out. Do simple validation of data entered into a Book ; for
example, accept ISBNs only of the form n-n-n-x where n is an integer and
x is a digit or a letter. Store an ISBN as a string.


This is the code I have right now:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// chapter9ex5-9.cpp : Defines the entry point for the console application.
// Osman Zakir
// 1 / 18 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 9 Exercises 5 - 9
// Exercise Specifications:
// Exercise 5:
/**
 * This exercise and the next few require you to design and implement a
 * Book class, such as you can imagine as part of software for a library. Class Book 
 * should have members for the ISBN, title, author, and copyright
 * date. Also store data on whether or not the book is checked out. Create
 * functions for returning those data values. Create functions for checking
 * a book in and out. Do simple validation of data entered into a Book; for
 * example, accept ISBNs only of the form n-n-n-x where n is an integer and
 * x is a digit or a letter. Store an ISBN as a string 
 */

#include "../../std_lib_facilities.h"

class Book
{
private:
	string m_title;
	string m_author;
	string m_isbn;
	int m_copyright_date;
	bool m_checked_in;
public:
	const string& get_title() const { return m_title; }
	const string& get_author() const { return m_author; }
	const string& get_isbn() const { return m_isbn; }
	int get_copyright_date() const { return m_copyright_date; }
	bool get_checked_state() const { return m_checked_in; }
	void set_checked(const bool state) { m_checked_in = state; }
	void check_book_in() { set_checked(true); }
	void check_book_out() { set_checked(false); }
	void validate_isbn(const string &isbn);
	void validate_title(const string &title);
	void validate_author(const string &author);
	void validate_copyright_date(const int copyright_date);
	Book(const string &title, const string &author, const string &isbn, const int copyright, const bool checked);
	Book();
};

void print_book_info(const Book &book);
void test();

int main()
{
    return 0;
}

Book::Book(const string &title, const string &author, const string &isbn, const int copyright, const bool checked)
	:m_title{ title }, m_author{ author }, m_isbn{ isbn }, m_copyright_date{ copyright }, m_checked_in{ checked }
{
	validate_isbn(isbn);
	//validate_title(title);
	//validate_author(author);
	//validate_copyright_date(copyright);
}

Book::Book()
	:m_title{}, m_author{}, m_isbn{}, m_copyright_date{}, m_checked_in{ true }
{
}

void Book::validate_isbn(const string &isbn)
{
	stringstream ss{ isbn };
	const char valid = '-';
	const char invalid = ' ';
	for (unsigned i = 0; i < isbn.size(); ++i)
	{
		if (!isdigit(isbn[i]) || !isalpha(isbn[i]))
		{
			error("invalid ISBN!");
		}
	}

	// check first three sections of ISBN to see if they're numbers followed by '-'
	for (int i = 0; i < 3; ++i)
	{
		int number = -1;
		char character = invalid;	// initialize both to "bad" just in case
		ss >> number >> character;
		if (number < 0 || character != valid)
		{
			error("invalid ISBN!");
		}
	}
	string final;
	ss >> final;	// check remaining character(s)
	if (final.size() != 1)
	{
		error("invalid ISBN!");		// no more than 1 character (number/letter) allowed here
	}
	else if (!isdigit(final[0]) && !isalpha(final[0]))	// separate check to prevent range-errors
	{
		error("invalid ISBN!");
	}
}


I got the ISBN validation code from another site, by the way.

Anyway, could someone give me hints on how to validate the title, author and copyright date? Just give me hints and guidance. I don't want the answer/solution on a silver platter.

Some advice and constructive criticism on the Book class would be appreciated, too. Like if what I have is good and if the public interface isn't exposing too much or too little.

I want to keep everything for all of the exercises for "Book" in this thread if possible, by the way. Thanks in advance.

[Note: The validation function calls in the Book class constructor definition are commented out to make sure that the code compiles without Linker errors since those validation functions haven't been defined yet.]
Last edited on
Why is your copyright date an int instead of a Date? Most of this chapter involved creating a Date class, so why aren't you using this class that you developed when reading the chapter?

I got the ISBN validation code from another site, by the way.

Why? I suggest you write your own version, you'll learn more by writing the program yourself than you will by copying someone else's work and you may even be able to simplify the function in the process. You may even want to consider making an ISBN class that can encapsulate all the error checking to make your "Book" class simpler.

As written there is no reason to have your validate_isbn() function a member function. You're passing a const string to this function so there is no real reason to make this a member function. Also I would think that this function should be returning something to the calling function to indicate success or failure and let the calling function worry about how to handle success or failure.






So it'd be better to use a Date for that after all? I did exercise 10 already, so maybe I can use the version that I implemented the leapyear() function for. I've seen that most publication/copyright dates are just the year rather than the full date, so I thought I'd make it an int holding the year.

As for the ISBN validation function, I did try to understand how it works so that I could maybe learn something from it. I had made a stupid change to it that I had to fix after giving it some thought, though. The code
1
2
3
4
5
6
7
8
9
10
stringstream ss{ isbn };
const char valid = '-';
const char invalid = ' ';
for (unsigned i = 0; i < isbn.size(); ++i)
{
	if (!isdigit(isbn[i]) || !isalpha(isbn[i]))
	{
		error("invalid ISBN!");
	}
}


Is now like this:

1
2
3
4
5
6
7
8
9
10
11
stringstream ss{ isbn };
const char valid = '-';
const char invalid = ' ';
const int printable = 33;
for (unsigned i = 0; i < isbn.size(); ++i)
{
	if (isbn[i] < printable)
	{
		error("invalid ISBN!");
	}
}


Because that's closer to how it was; it's just that the const keyword was added by me since I think a "symbolic constant" should be marked as const. That's also what Stroustrup said in PPP2, after all.

Should I make the validation functions friends of the class, or is it better if they can't see the private data of the class? And if they should return something to indicate success or failure, how about I make them of type bool? Though I want to use error() in those functions so that I can catch the exception in main (test() will be the function calling the constructor function, but I want to leave the error-handling up to main).

Maybe it'd be good if the validation function is of type bool, but I also use error() to throw an exception instead of returning false? So if it's a success, it'll return true, but if there's an error it'll throw.

I've made some changes to the code. I'll make the changes to the validation functions that I'm asking about here after I get an answer. I don't get how to do the author name and book title validations, though. I tried doing the author name one by using a stringstream and checking the characters to see if it's a character, a letter, or a space, but it kept saying that the author name is invalid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// chapter9ex5-9.cpp : Defines the entry point for the console application.
// Osman Zakir
// 1 / 18 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 9 Exercises 5 - 9
// Exercise Specifications:
// Exercise 5:
/**
 * This exercise and the next few require you to design and implement a
 * Book class, such as you can imagine as part of software for a library. Class Book 
 * should have members for the ISBN, title, author, and copyright
 * date. Also store data on whether or not the book is checked out. Create
 * functions for returning those data values. Create functions for checking
 * a book in and out. Do simple validation of data entered into a Book; for
 * example, accept ISBNs only of the form n-n-n-x where n is an integer and
 * x is a digit or a letter. Store an ISBN as a string 
 */

#include "../../std_lib_facilities.h"

class Book
{
private:
	string m_title;
	string m_author;
	string m_isbn;
	int m_copyright_date;
	bool m_checked_in;
public:
	const string& get_title() const { return m_title; }
	const string& get_author() const { return m_author; }
	const string& get_isbn() const { return m_isbn; }
	int get_copyright_date() const { return m_copyright_date; }
	bool get_checked_state() const { return m_checked_in; }
	void set_checked(const bool state) { m_checked_in = state; }
	void check_book_in() { set_checked(true); }
	void check_book_out() { set_checked(false); }
	void validate_isbn();
	void validate_copyright_date();
	Book(const string &title, const string &author, const string &isbn, const int copyright, const bool checked);
	Book();
};

void print_book_info(const Book &book);
void test();

int main()
{
	try
	{
		test();
		keep_window_open();
	}
	catch (const runtime_error &e)
	{
		cerr << "error: " << e.what() << '\n';
		keep_window_open();
		return 1;
	}
}

Book::Book(const string &title, const string &author, const string &isbn, const int copyright, const bool checked)
	:m_title{ title }, m_author{ author }, m_isbn{ isbn }, m_copyright_date{ copyright }, m_checked_in{ checked }
{
	validate_isbn();
	validate_copyright_date();
}

Book::Book()
	:m_title{}, m_author{}, m_isbn{}, m_copyright_date{}, m_checked_in{ true }
{
}

void Book::validate_isbn()
{
	stringstream ss{ m_isbn };
	const char valid = '-';
	const char invalid = ' ';
	const int printable = 33;
	for (unsigned i = 0; i < m_isbn.size(); ++i)
	{
		if (m_isbn[i] < printable)
		{
			error("invalid ISBN!");
		}
	}

	// check first three sections of ISBN to see if they're numbers followed by '-'
	for (int i = 0; i < 3; ++i)
	{
		int number = -1;
		char character = invalid;	// initialize both to "bad" just in case
		ss >> number >> character;
		if (number < 0 || character != valid)
		{
			error("invalid ISBN!");
		}
	}
	string final;
	ss >> final;	// check remaining character(s)
	if (final.size() != 1)
	{
		error("invalid ISBN!");		// no more than 1 character (number/letter) allowed here
	}
	else if (!isdigit(final[0]) && !isalpha(final[0]))	// separate check to prevent range-errors
	{
		error("invalid ISBN!");
	}
}

void Book::validate_copyright_date()
{
	if (m_copyright_date < 0)
	{
		error("invalid copyright date!");
	}
}

void test()
{
	Book book1{ "MyBook", "William Copperfield", "5-1-2-X", 2001, true };
	Book book2{ "Dummy", "Some Author", "7-6-4-8", 2001, false };
	cout << "Info for book1:\n";
	print_book_info(book1);
	cout << "\nInfo for book2:\n";
	print_book_info(book2);
}

void print_book_info(const Book &book)
{
	cout << "Book Title: " << book.get_title() << '\n'
		<< "Book Author: " << book.get_author() << '\n'
		<< "Book ISBN: " << book.get_isbn() << '\n'
		<< "Book Copyright Date: " << book.get_copyright_date() << '\n';
	if (book.get_checked_state())
	{
		cout << "Book is not checked out.\n";
	}
	else
	{
		cout << "Book is checked out.\n";
	}
}


Is the code for the public interface fine aside from me needing to make the validation functions as helper functions rather than member functions?

Do you want to see my code for the Date class? It's the one with the Chrono namespace shown at the end of the chapter. I took out the functions that weren't defined yet. So it doesn't have add_month(), add_day() and add_year().
Last edited on
Look at this function signature:
void Book::validate_copyright_date(const int copyright)
Why are you passing a value into this member function instead of just using the member variable? It makes no sense to have a class member function that makes no use of any of the class member variables.

Though I want to use error() in those functions so that I can catch the exception in main (test() will be the function calling the constructor function, but I want to leave the error-handling up to main).

Why?


As for the ISBN validation function, I did try to understand how it works so that I could maybe learn something from it. I had made a stupid change to it that I had to fix after giving it some thought, though. The code

Have you considered using some of the string helper functions to validate this variable? Consider using things like std::string.find_first_of(), std::string.find.first_not_of(), std::string.back(), etc.


Last edited on
Yeah, I've taken those explicit arguments out of the validation functions now. What you said makes sense.

I'll have to study up on those string functions you mentioned. Thanks for that, by the way. Could I use those for the author name and book title validation as well? Or should I not bother trying validate those?

Anyway, I'll post the code here for reference.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// chapter9ex5-9.cpp : Defines the entry point for the console application.
// Osman Zakir
// 1 / 18 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 9 Exercises 5 - 9
// Exercise Specifications:
// Exercise 5:
/**
 * This exercise and the next few require you to design and implement a
 * Book class, such as you can imagine as part of software for a library. Class Book 
 * should have members for the ISBN, title, author, and copyright
 * date. Also store data on whether or not the book is checked out. Create
 * functions for returning those data values. Create functions for checking
 * a book in and out. Do simple validation of data entered into a Book; for
 * example, accept ISBNs only of the form n-n-n-x where n is an integer and
 * x is a digit or a letter. Store an ISBN as a string 
 */

#include "../../std_lib_facilities.h"

class Book
{
private:
	string m_title;
	string m_author;
	string m_isbn;
	int m_copyright_date;
	bool m_checked_in;
public:
	const string& get_title() const { return m_title; }
	const string& get_author() const { return m_author; }
	const string& get_isbn() const { return m_isbn; }
	int get_copyright_date() const { return m_copyright_date; }
	bool get_checked_state() const { return m_checked_in; }
	void set_checked(const bool state) { m_checked_in = state; }
	void check_book_in() { set_checked(true); }
	void check_book_out() { set_checked(false); }
	void validate_isbn();
	void validate_copyright_date();
	Book(const string &title, const string &author, const string &isbn, const int copyright, const bool checked);
	Book();
};

void print_book_info(const Book &book);
void test();

int main()
{
	try
	{
		test();
		keep_window_open();
	}
	catch (const runtime_error &e)
	{
		cerr << "error: " << e.what() << '\n';
		keep_window_open();
		return 1;
	}
}

Book::Book(const string &title, const string &author, const string &isbn, const int copyright, const bool checked)
	:m_title{ title }, m_author{ author }, m_isbn{ isbn }, m_copyright_date{ copyright }, m_checked_in{ checked }
{
	validate_isbn();
	validate_copyright_date();
}

Book::Book()
	:m_title{}, m_author{}, m_isbn{}, m_copyright_date{}, m_checked_in{ true }
{
}

void Book::validate_isbn()
{
	stringstream ss{ m_isbn };
	const char valid = '-';
	const char invalid = ' ';
	const int printable = 33;
	for (unsigned i = 0; i < m_isbn.size(); ++i)
	{
		if (m_isbn[i] < printable)
		{
			error("invalid ISBN!");
		}
	}

	// check first three sections of ISBN to see if they're numbers followed by '-'
	for (int i = 0; i < 3; ++i)
	{
		int number = -1;
		char character = invalid;	// initialize both to "bad" just in case
		ss >> number >> character;
		if (number < 0 || character != valid)
		{
			error("invalid ISBN!");
		}
	}
	string final;
	ss >> final;	// check remaining character(s)
	if (final.size() != 1)
	{
		error("invalid ISBN!");		// no more than 1 character (number/letter) allowed here
	}
	else if (!isdigit(final[0]) && !isalpha(final[0]))	// separate check to prevent range-errors
	{
		error("invalid ISBN!");
	}
}

void Book::validate_copyright_date()
{
	if (m_copyright_date < 0)
	{
		error("invalid copyright date!");
	}
}

void test()
{
	Book book1{ "MyBook", "William Copperfield", "5-1-2-X", 2001, true };
	Book book2{ "Dummy", "Some Author", "7-6-4-8", 2001, false };
	cout << "Info for book1:\n";
	print_book_info(book1);
	cout << "\nInfo for book2:\n";
	print_book_info(book2);
}

void print_book_info(const Book &book)
{
	cout << "Book Title: " << book.get_title() << '\n'
		<< "Book Author: " << book.get_author() << '\n'
		<< "Book ISBN: " << book.get_isbn() << '\n'
		<< "Book Copyright Date: " << book.get_copyright_date() << '\n';
	if (book.get_checked_state())
	{
		cout << "Book is not checked out.\n";
	}
	else
	{
		cout << "Book is checked out.\n";
	}
}


As for using error(), are you saying I shouldn't do that for the validation functions and just return false upon failure? Or I could have the constructor use error() if any of the validation tests fail.
Last edited on
As for using error(), are you saying I shouldn't do that for the validation functions and just return false upon failure?

No, I'm saying that if you use exceptions you should be catching the exceptions as close to the problem as possible, especially if you want to be able to "recover" from the exception. For example your test() function should look more like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void test()
{
    try
    {
        cout << "Info for book1:\n";
        Book book1{ "MyBook", "William Copperfield", "5-1-2-X", 2001, true };

        print_book_info(book1);
    }
    catch(const runtime_error &e)
    {
        cerr << "error: " << e.what() << '\n';
    }

    try
    {
        cout << "\nInfo for book2:\n";
        Book book2{ "Dummy", "Some Author", "7-6-4-8", -2001, false };


        print_book_info(book2);
    }
    catch(const runtime_error &e)
    {
        cerr << "error: " << e.what() << '\n';
    }
}

This way you can test all the tests and the failures will be reported to the screen and the program will continue to the next "test". In a "real" program you would probably want to try to fix the problems and then proceed to the next item.

You're validation functions could (probably should) be private member functions since they should only be called by other member functions.

Could I use those for the author name and book title validation as well? Or should I not bother trying validate those?


To answer this question you need to know what makes a good or bad name or title. If you don't know when a name or title is good or bad then you probably don't need to validate these objects.


I noticed that the expression calculator program from Chapters 6 and 7 recovers from errors quite nicely. It'd be good if I could do something similar. Maybe I could have the user enter valid information for the book in case the test() function successfully catches an exception.

I tried doing that in the Rational class program but decided to give up because I couldn't get it to work. What I tried was to do a loop in the try-catch block where the loop would continue as long as the input is still bad/invalid. It'd run correctly until it's near the end, when it's printed the values, but then it'd go into an infinite loop and I'd have to kill the program.

Anyway, back to the Book class. I'll move the error handling to test() and only have main() call test() and nothing more. Or main() could handle any errors that test() couldn't for any reason (catch (const exception &e) and catch (...)).

I'll try the changes and then get back to you later.

Edit: Okay, I took out the validation function for the copyright date and changed the copyright date object from type int to type Date. I added validation for the year in is_date() function and am having it throw Chrono::Date::Invalid when the year is negative in addition to everything else. So now the copyright date validation is handled by the Date class and I just catch Chrono::Date::Invalid in my test() function if there's an invalid copyright date.

For the author name, would it be good to not accept a string without letters, spaces, hyphens or dots? Or should I not bother since some people might use an Arabian number instead of a Roman Numeral (like when someone has a name like "So and So II" (II meaning "The Second") and they use the number 2 instead of the Roman Numeral "II"). Similar for the title of the book.

Anyway, if you want to see the Date class code I have, let me know and I'll post it. For now I'll post the Book class program code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// chapter9ex5-9.cpp : Defines the entry point for the console application.
// Osman Zakir
// 1 / 18 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 9 Exercises 5 - 9
// Exercise Specifications:
// Exercise 5:
/**
 * This exercise and the next few require you to design and implement a
 * Book class, such as you can imagine as part of software for a library. Class Book 
 * should have members for the ISBN, title, author, and copyright
 * date. Also store data on whether or not the book is checked out. Create
 * functions for returning those data values. Create functions for checking
 * a book in and out. Do simple validation of data entered into a Book; for
 * example, accept ISBNs only of the form n-n-n-x where n is an integer and
 * x is a digit or a letter. Store an ISBN as a string 
 */

#include "../../std_lib_facilities.h"
#include "Chrono.h"

using namespace Chrono;

class Book
{
private:
	string m_title;
	string m_author;
	string m_isbn;
	Date m_copyright_date;
	bool m_checked_in;
	void validate_isbn();
public:
	const string& get_title() const { return m_title; }
	const string& get_author() const { return m_author; }
	const string& get_isbn() const { return m_isbn; }
	const Date& get_copyright_date() const { return m_copyright_date; }
	bool get_checked_state() const { return m_checked_in; }
	void set_checked(const bool state) { m_checked_in = state; }
	void check_book_in() { set_checked(true); }
	void check_book_out() { set_checked(false); }
	Book(const string &title, const string &author, const string &isbn, const Date &copyright, const bool checked);
	Book();
};

void print_book_info(const Book &book);
void test();

int main()
{
	try
	{
		test();
		keep_window_open();
	}
	catch (const exception &e)
	{
		cerr << "error: " << e.what() << '\n';
		keep_window_open();
		return 1;
	}
	catch (...)
	{
		cerr << "error: unknown exception ocurred!\n";
		keep_window_open();
		return 2;
	}
}

Book::Book(const string &title, const string &author, const string &isbn, const Date &copyright, const bool checked)
	:m_title{ title }, m_author{ author }, m_isbn{ isbn }, m_copyright_date{ copyright }, m_checked_in{ checked }
{
	validate_isbn();
}

Book::Book()
	:m_title{}, m_author{}, m_isbn{}, m_copyright_date{}, m_checked_in{ true }
{
}

void Book::validate_isbn()
{
	stringstream ss{ m_isbn };
	const char valid = '-';
	const char invalid = ' ';
	const int printable = 33;
	for (unsigned i = 0; i < m_isbn.size(); ++i)
	{
		if (m_isbn[i] < printable)
		{
			error("invalid ISBN!");
		}
	}

	// check first three sections of ISBN to see if they're numbers followed by '-'
	for (int i = 0; i < 3; ++i)
	{
		int number = -1;
		char character = invalid;	// initialize both to "bad" just in case
		ss >> number >> character;
		if (number < 0 || character != valid)
		{
			error("invalid ISBN!");
		}
	}
	string final;
	ss >> final;	// check remaining character(s)
	if (final.size() != 1)
	{
		error("invalid ISBN!");		// no more than 1 character (number/letter) allowed here
	}
	else if (!isdigit(final[0]) && !isalpha(final[0]))	// separate check to prevent range-errors
	{
		error("invalid ISBN!");
	}
}

void test()
{
	
	try
	{
		Date copyright{ 2001, Month::apr, 25 };
		Book book1{ "MyBook", "William Copperfield", "5-1-2-X", copyright, true };
		cout << "Info for book1:\n";
		print_book_info(book1);
	}
	catch (const runtime_error &e)
	{
		cerr << "book1 error: " << e.what() << '\n';
	}
	catch (Date::Invalid)
	{
		cerr << "book1 error: copyright date is invalid!\n";
	}
	
	try
	{
		Date copyright{ 2001, Month::mar, 5 };
		Book book2{ "Dummy", "Some Author", "7-6-4-8", copyright, false };
		cout << "\nInfo for book2:\n";
		print_book_info(book2);
	}
	catch (const runtime_error &e)
	{
		cerr << "book2 error: " << e.what() << "\n\n";
	}
	catch (Date::Invalid)
	{
		cerr << "book2 error: copyright date is invalid!\n\n";
	}
}

void print_book_info(const Book &book)
{
	cout << "Book Title: " << book.get_title() << '\n'
		<< "Book Author: " << book.get_author() << '\n'
		<< "Book ISBN: " << book.get_isbn() << '\n'
		<< "Book Copyright Date: " << book.get_copyright_date() << '\n';
	if (book.get_checked_state())
	{
		cout << "Book is not checked out.\n\n";
	}
	else
	{
		cout << "Book is checked out.\n\n";
	}
}
Last edited on
Sorry for the double-post, but I've updated the other post to include my updated code and some other info. Please read. Thank you.
For the author name, would it be good to not accept a string without letters, spaces, hyphens or dots?

Probably not. A name can contain spaces and or hyphens and or period and while a name without letters may seem strange, remember we live in a strange world.

Anyway, if you want to see the Date class code I have, let me know and I'll post it. For now I'll post the Book class program code:

In your book constructor you should be able to construct the Date class instance without the temporary. Something like the following?

Book book2{ "Dummy", "Some Author", "7-6-4-8", { 2001, Month::mar, 5 }, false };

I added validation for the year in is_date() function and am having it throw Chrono::Date::Invalid when the year is negative in addition to everything else.

What does "everything else" mean?
"Everything else" means all the code Stroustrup gave at the end of Chapter 9 for Date. I used it as is except for taking add_day(), add_month() and add_year() out and defining leapyear() as per exercise 10. If I need to, I'll try implementing those other three functions for this program.

Would it not be good to leave the temporaries for the Date objects as is if I want to try taking user input for the book info?

I just did exercise 6, by the way. I'll make the change you suggested and then post the code in a bit.

Edit: Here's the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// chapter9ex5-9.cpp : Defines the entry point for the console application.
// Osman Zakir
// 1 / 18 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 9 Exercises 5 - 9
// Exercise Specifications:
// Exercise 5:
/**
 * This exercise and the next few require you to design and implement a
 * Book class, such as you can imagine as part of software for a library. Class Book 
 * should have members for the ISBN, title, author, and copyright
 * date. Also store data on whether or not the book is checked out. Create
 * functions for returning those data values. Create functions for checking
 * a book in and out. Do simple validation of data entered into a Book; for
 * example, accept ISBNs only of the form n-n-n-x where n is an integer and
 * x is a digit or a letter. Store an ISBN as a string 
 */
// Exercise 6:
/**
 * Add operators for the Book class. Have the == operator check whether
 * the ISBN numbers are the same for two books. Have != also compare
 * the ISBN numbers. Have a << print out the title, author, and ISBN on
 * separate lines.
 */

#include "../../std_lib_facilities.h"
#include "Chrono.h"

using namespace Chrono;

class Book
{
private:
	string m_title;
	string m_author;
	string m_isbn;
	Date m_copyright_date;
	bool m_checked_in;
	void validate_isbn();
public:
	const string& get_title() const { return m_title; }
	const string& get_author() const { return m_author; }
	const string& get_isbn() const { return m_isbn; }
	const Date& get_copyright_date() const { return m_copyright_date; }
	bool get_checked_state() const { return m_checked_in; }
	void set_checked(const bool state) { m_checked_in = state; }
	void check_book_in() { set_checked(true); }
	void check_book_out() { set_checked(false); }
	Book(const string &title, const string &author, const string &isbn, const Date &copyright, const bool checked);
	Book();
};

ostream& operator<<(ostream &os, const Book &book);
bool operator==(const Book &book1, const Book &book2);
bool operator!=(const Book &book1, const Book &book2);
void print_book_info(const Book &book);
void test();

int main()
{
	try
	{
		test();
		keep_window_open();
	}
	catch (const exception &e)
	{
		cerr << "error: " << e.what() << '\n';
		keep_window_open();
		return 1;
	}
	catch (...)
	{
		cerr << "error: unknown exception ocurred!\n";
		keep_window_open();
		return 2;
	}
}

Book::Book(const string &title, const string &author, const string &isbn, const Date &copyright, const bool checked)
	:m_title{ title }, m_author{ author }, m_isbn{ isbn }, m_copyright_date{ copyright }, m_checked_in{ checked }
{
	validate_isbn();
}

Book::Book()
	:m_title{}, m_author{}, m_isbn{}, m_copyright_date{}, m_checked_in{ true }
{
}

void Book::validate_isbn()
{
	stringstream ss{ m_isbn };
	const char valid = '-';
	const char invalid = ' ';
	const int printable = 33;
	for (unsigned i = 0; i < m_isbn.size(); ++i)
	{
		if (m_isbn[i] < printable)
		{
			error("invalid ISBN!");
		}
	}

	// check first three sections of ISBN to see if they're numbers followed by '-'
	for (int i = 0; i < 3; ++i)
	{
		int number = -1;
		char character = invalid;	// initialize both to "bad" just in case
		ss >> number >> character;
		if (number < 0 || character != valid)
		{
			error("invalid ISBN!");
		}
	}
	string final;
	ss >> final;	// check remaining character(s)
	if (final.size() != 1)
	{
		error("invalid ISBN!");		// no more than 1 character (number/letter) allowed here
	}
	else if (!isdigit(final[0]) && !isalpha(final[0]))	// separate check to prevent range-errors
	{
		error("invalid ISBN!");
	}
}

ostream& operator<<(ostream &os, const Book &book)
{
	return os << "Book Title: " << book.get_title() << '\n'
		<< "Book Author: " << book.get_author() << '\n'
		<< "Book ISBN: " << book.get_isbn() << '\n';
}

bool operator==(const Book &book1, const Book &book2)
{
	return book1.get_isbn() == book2.get_isbn();
}

bool operator!=(const Book &book1, const Book &book2)
{
	return !(book1 == book2);
}

void test()
{
	Book book1;
	try
	{
		book1 = { "MyBook", "William Copperfield", "5-1-2-X", { 2001, Month::apr, 25 }, true };
		cout << "Info for book1:\n";
		print_book_info(book1);
	}
	catch (const runtime_error &e)
	{
		cerr << "book1 error: " << e.what() << '\n';
	}
	catch (Date::Invalid)
	{
		cerr << "book1 error: copyright date is invalid!\n";
	}
	
	Book book2;
	try
	{
		book2 = { "Dummy", "Some Author", "7-6-4-8", { 2001, Month::mar, 5 }, false };
		cout << "\nInfo for book2:\n";
		print_book_info(book2);
	}
	catch (const runtime_error &e)
	{
		cerr << "book2 error: " << e.what() << "\n\n";
		cin.clear();
		cin.ignore();
	}
	catch (Date::Invalid)
	{
		cerr << "book2 error: copyright date is invalid!\n\n";
		cin.clear();
		cin.ignore();
	}

	try
	{
		cout << "Would you like to quit the program now ('q' or 'Q'),\n"
			<< "Or would you like to either compare two books' ISBNs to see"
			<< " if there are two different-titled books with the same ISBN ('a' or 'A'),\n"
			<< "or to see which book(s) are checked out and which aren't ('b' or 'B')?\n";
		char response;
		cin >> response;
		cin.ignore();
		if (response == 'a' || response == 'A')
		{
			// book1 == book2 will compare ISBNs only, so we need a separate check for title
			if (book1 == book2 && book1.get_title() != book2.get_title())
			{
				cout << "Books book1 and book2 are different-titled books with the same ISBN\n";
			}
			else if (book1 == book2 && book1.get_title() == book2.get_title())
			{
				cout << "No books are different-titled but of the same ISBN as each other\n"
					<< "But there are two same-titled books with different ISBNs (which is fine)\n";
			}
			else if (book1 != book2)
			{
				cout << "No books with the same ISBN here\n";
			}
		}
		else if (response == 'b' || response == 'B')
		{
			cout << "Checking...\n\nDone checking:\n";
			if (book1.get_checked_state())
			{
				cout << "Book " << book1.get_title() << " is not checked out\n";
			}
			else
			{
				cout << "Book " << book1.get_title() << " is checked out\n";
			}

			if (book2.get_checked_state())
			{
				cout << "Book " << book2.get_title() << " is not checked out\n";
			}
			else
			{
				cout << "Book " << book2.get_title() << " is checked out\n";
			}
		}
		else if (response == 'q' || response == 'Q')
		{
			return;
		}
		else
		{
			error("That's invalid input! Please try again later.");
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "error: " << e.what() << '\n';
		cin.clear();
	}
}

void print_book_info(const Book &book)
{
	cout << book << '\n';
}
Last edited on

Would it not be good to leave the temporaries for the Date objects as is if I want to try taking user input for the book info?

What? If you need to get input from the user you'll either need to create "setter" functions or use temporaries. But when using constants prefer initialization lists over temporaries to avoid the copy caused by using the temporary.

EDIT:

1
2
3
	cout << "Would you like to quit the program now ('q' or 'Q'),\n"
			<< "Or would you like to either compare two books' ISBNs to see"
			<< " if there are two different-titled books with the same ISBN ('a' or 'A'),\n"

By the way it is possible to have two different-titled books by different authors with the same ISBN number. See this link:
https://en.wikipedia.org/wiki/International_Standard_Book_Number
See the section titled: "Errors in usage".
Last edited on
By the way it is possible to have two different-titled books by different authors with the same ISBN number. See this link:
https://en.wikipedia.org/wiki/International_Standard_Book_Number
See the section titled: "Errors in usage".


Huh. That's news to me. Well, it says that some publishers fail to check and that that causes problems for libraries. A library should check, though, right? So I'll check.

By the way, I'm asking this just in case, but: So far, so good? My Book class, I mean. If I still have something in there I shouldn't or if I've done something in a sloppy way that I have yet to fix, let me know. And I'm sorry if the code is messy. I'll try to fix it if you think it's messy.

Edit: I think I need to make another change. Instead of just printing all of the info for all of the books I have from the get-go, I'll ask the user if he/she wants to see the info printed.
Last edited on
This is a god projt. Gives me an idea thanks :)
@jlb: I got the stuff for all of the exercises for the Book class to work, except the last one. For some reason, constructors for Transaction and Library won't work.

I'll post code for the class declarations and stuff first and then some of the member function definitions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Patron
{
private:
	string m_user_name;
	string m_lib_card_num;
	bool m_fee_owed;
	double m_fee;
public:
	Patron(const string &user_name, const string &lib_card_num, const bool fee_owed);
	Patron();
	const string& get_user_name() const { return m_user_name; }
	const string& get_card_num() const { return m_lib_card_num; }
	bool get_fee_state() const { return m_fee_owed; }
	void set_fee(double fee_amount) { m_fee = fee_amount; }
};

enum class Genre
{
	fiction, nonfiction, periodical, biography, children
};

class Book
{
private:
	string m_title;
	string m_author;
	string m_isbn;
	Date m_copyright_date;
	Genre m_genre;
	bool m_checked_in;
	void validate_isbn();
public:
	const string& get_title() const { return m_title; }
	const string& get_author() const { return m_author; }
	const string& get_isbn() const { return m_isbn; }
	const Date& get_copyright_date() const { return m_copyright_date; }
	const Genre& get_genre() const { return m_genre; }
	bool get_checked_state() const { return m_checked_in; }
	void set_checked_in(const bool state) { m_checked_in = state; }
	Book(const string &title, const string &author, const string &isbn, 
		const Date &copyright_date, const bool checked_in, const Genre &genre);
	Book();
};

struct Transaction
{
	Book &m_book;
	Patron &m_patron;
	Date &m_trans_date;	// transaction date
	vector<Transaction> transactions;
	Transaction(const Book &book, const Patron &patron, const Date &trans_date);
};

class Library
{
private:
	vector<Book> &books;
	vector<Patron> &patrons;
	vector<Transaction> &transactions;
	bool book_in_library(const Book &book);
	bool patron_in_library(const Patron &patron);
public:
	vector<Book> add_book(const Book &book);
	vector<Patron> add_patron(const Patron &patron);
	vector<Patron> fee_owed_patrons();
	void check_book_in(Book &book) { book.set_checked_in(true); }
	void check_book_out(Book &book, Patron &patron);
	Library();
};


Patron constructors:
1
2
3
4
5
6
7
8
9
Patron::Patron(const string &user_name, const string &lib_card_num, const bool fee_owed)
	:m_user_name{ user_name }, m_lib_card_num{ lib_card_num }, m_fee_owed{ fee_owed }
{
}

Patron::Patron()
	: m_user_name{}, m_lib_card_num{}, m_fee_owed{ false }
{
}


Library functions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
vector<Patron> Library::fee_owed_patrons()
{
	vector<Patron> fee_owed;
	for (const auto &patron : patrons)
	{
		if (patron.get_fee_state())
		{
			fee_owed.push_back(patron);
		}
	}
	return fee_owed;
}

bool Library::book_in_library(const Book &book)
{
	for (unsigned i = 0; i < books.size(); ++i)
	{
		if (book == books[i])
		{
			return true;
		}
	}
	return false;
}

bool Library::patron_in_library(const Patron &patron)
{
	for (unsigned i = 0; i < patrons.size(); ++i)
	{
		if (patron == patrons[i])
		{
			return true;
		}
	}
	return false;
}

bool is_fee_owed(const Patron &patron)
{
	if (patron.get_fee_state())
	{
		return true;
	}
	return false;
}

void test()
{
	Book book1;
	try
	{
		book1 = { "MyBook", "William Copperfield", "5-1-2-X",
		{ 2001, Month::apr, 25 }, true, Genre::fiction };
	}
	catch (const runtime_error &e)
	{
		cerr << "book1 error: " << e.what() << '\n';
		cin.clear();
	}
	catch (Date::Invalid)
	{
		cerr << "book1 error: copyright date is invalid!\n";
		cin.clear();
	}
	
	Book book2;
	try
	{
		book2 = { "Dummy", "Some Author", "7-6-4-8",
		{ 2001, Month::mar, 5 }, false, Genre::children };
	}
	catch (const runtime_error &e)
	{
		cerr << "book2 error: " << e.what() << "\n\n";
		cin.clear();
	}
	catch (Date::Invalid)
	{
		cerr << "book2 error: copyright date is invalid!\n\n";
		cin.clear();
	}

	try
	{
		options_screen(book1, book2);
	}
	catch (const runtime_error &e)
	{
		cerr << "error: " << e.what() << '\n';
		cin.clear();
	}

	Patron patron1;
	Library library;
	library.add_book(book1);
	library.add_patron(patron1);
	try
	{
		patron1 = { "Frank Williams", "1246-56311-5600", false };
		if (is_fee_owed(patron1))
		{
			error("this patron owes a fee!");
		}
		else
		{
			library.check_book_out(book1, patron1);
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "error: " << e.what() << '\n';
		cin.clear();
	}
	if (!is_fee_owed(patron1))
	{
		Transaction transaction{ book1, patron1,{ 2017, Month::jan, 1 } };
		transaction.transactions.push_back(transaction);
	}

	Patron patron2{ "John Doe", "5674-99995-0000", true };
	library.add_patron(patron2);
	patron2.set_fee(100);
	try
	{
		if (is_fee_owed(patron2))
		{
			error("this patron owes a fee!");
		}
		else
		{
			library.check_book_out(book2, patron2);
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "error: " << e.what() << '\n';
		cin.clear();
	}

	if (!is_fee_owed(patron2))
	{
		Transaction transaction{ book2, patron2, {2017, Month::jan, 21} };
		transaction.transactions.push_back(transaction);
	}
}

vector<Book> Library::add_book(const Book &book)
{
	books.push_back(book);
	return books;
}

vector<Patron> Library::add_patron(const Patron &patron)
{
	patrons.push_back(patron);
	return patrons;
}

void Library::check_book_out(Book &book, Patron &patron)
{
	for (unsigned i = 0; i < patrons.size(); ++i)
	{
		if (book_in_library(book) && patron_in_library(patron))
		{
			book.set_checked_in(false);
		}
		else
		{
			error("book and/or patron not in library!");
		}
	}
}


This is the options_screen() test() calls:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void options_screen(const Book &book1, const Book &book2)
{
	cout << "Welcome to this library software program. Please pick from these three choices:\n";
	cout << "(A) See if there are books with the same ISBN with different titles and authors\n"
		<< "(B) See which books are checked out and which aren't\n"
		<< "(C) See information on all books in the library be printed to the screen\n";
	char response;
	cin >> response;
	cin.ignore();
	switch (response)
	{
	case 'A':
	case 'a':
		// book1 == book2 will compare ISBNs only, so we need a separate check for title
		if (book1 == book2 && book1.get_title() != book2.get_title())
		{
			cout << "Books book1 and book2 are different-titled books with the same ISBN\n";
		}
		else if (book1 == book2 && book1.get_title() == book2.get_title())
		{
			cout << "No books are different-titled but of the same ISBN as each other\n"
				<< "But there are two same-titled books with different ISBNs (which is fine)\n";
		}
		else if (book1 != book2)
		{
			cout << "No books with the same ISBN here\n";
		}
		break;
	case 'B':
	case 'b':
		cout << "Checking...\n\nDone checking:\n";
		if (book1.get_checked_state())
		{
			cout << "Book " << book1.get_title() << " is not checked out\n";
		}
		else
		{
			cout << "Book " << book1.get_title() << " is checked out\n";
		}

		if (book2.get_checked_state())
		{
			cout << "Book " << book2.get_title() << " is not checked out\n";
		}
		else
		{
			cout << "Book " << book2.get_title() << " is checked out\n";
		}
		break;
	case 'C':
	case 'c':
		cout << "Info on book1:\n";
		print_book_info(book1);
		cout << "\nInfo on book2:\n";
		print_book_info(book2);
		break;
	default:
		error("That's invalid input! Please try again later.");
		break;
	}
}


Could someone help me out? I can't initialize Library or Transaction objects at all.

If anyone wants to me to post the exercise specifications, just let me know and I'll post them.
Last edited on
I can't initialize Library or Transaction objects at all.

Why not? What kind of error messages are you getting?

Please explain the following snippet:
1
2
3
4
5
6
7
8
struct Transaction
{
	Book &m_book;
	Patron &m_patron;
	Date &m_trans_date;	// transaction date
	vector<Transaction> transactions;
	Transaction(const Book &book, const Patron &patron, const Date &trans_date);
};

Particularly why all the references?

I read in another tutorial before that in times where a class has objects that shouldn't be destroyed with the class, it's better to put those objects in as pointers or references. I wanted to try that, but it didn't work. I guess I forgot how to do it. I've taken the references out and put them in as regular objects. It works now.

The errors I was getting said that those objects can be initialized because their constructors are deleted functions. When I tried to define constructors, it said that the initializer lists won't work.

Here's the code as it is now (The code is too big to post all at once, so I'll post it in pieces again; please let me know if the code in test() is sloppy or if friending it to the Library class is bad in terms of encapsulation):

Library, Transaction, Book, Patron Genre declarations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class Patron
{
private:
	string m_user_name;
	string m_lib_card_num;
	bool m_fee_owed;
	double m_fee;
public:
	Patron(const string &user_name, const string &lib_card_num, const bool fee_owed);
	Patron();
	const string& get_user_name() const { return m_user_name; }
	const string& get_card_num() const { return m_lib_card_num; }
	bool get_fee_state() const { return m_fee_owed; }
	void set_fee(double fee_amount) { m_fee = fee_amount; }
};

enum class Genre
{
	fiction, nonfiction, periodical, biography, children
};

class Book
{
private:
	string m_title;
	string m_author;
	string m_isbn;
	Date m_copyright_date;
	Genre m_genre;
	bool m_checked_in;
	void validate_isbn();
public:
	const string& get_title() const { return m_title; }
	const string& get_author() const { return m_author; }
	const string& get_isbn() const { return m_isbn; }
	const Date& get_copyright_date() const { return m_copyright_date; }
	const Genre& get_genre() const { return m_genre; }
	bool get_checked_state() const { return m_checked_in; }
	void set_checked_in(const bool state) { m_checked_in = state; }
	Book(const string &title, const string &author, const string &isbn, 
		const Date &copyright_date, const bool checked_in, const Genre &genre);
	Book();
};

struct Transaction
{
	Book m_book;
	Patron m_patron;
	Date m_trans_date;
	Transaction();
	Transaction(const Book &book, const Patron &patron, const Date &trans_date);
};

class Library
{
private:
	vector<Book> m_vbooks;
	vector<Patron> m_vpatrons;
	vector<Transaction> m_vtransactions;
public:
	void add_book(const Book &book) { m_vbooks.push_back(book); }
	void add_patron(const Patron &patron) { m_vpatrons.push_back(patron); }
	void check_book_in(Book &book, const Patron &patron);
	void check_book_out(Book &book, const Patron &patron);
	void add_transaction(const Transaction &transaction) { m_vtransactions.push_back(transaction); }
	bool patron_in_library(const Patron &patron);
	bool book_in_library(const Book &book);
	vector<Patron> fee_owed_patrons();
	friend void test();
};


test():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
void test()
{
	Book book1;
	Book book2;
	try
	{
		book1 = { "MyBook", "William Copperfield", "5-1-2-X",
		{ 2001, Month::apr, 25 }, false, Genre::fiction };
	}
	catch (const runtime_error &e)
	{
		cerr << "book1 error: " << e.what() << '\n';
		cin.clear();
	}
	catch (Date::Invalid)
	{
		cerr << "book1 error: copyright date is invalid!\n";
		cin.clear();
	}

	try
	{
		book2 = { "Dummy", "Some Author", "7-6-4-8",
		{ 2001, Month::mar, 5 }, false, Genre::children };
	}
	catch (const runtime_error &e)
	{
		cerr << "book2 error: " << e.what() << '\n';
		cin.clear();
	}
	catch (Date::Invalid)
	{
		cerr << "book2 error: copyright date is invalid!\n";
		cin.clear();
	}
		
	Patron patron1{ "John Doe", "1234-63984-6689", false };
	Patron patron2{ "Monkey D. Luffy", "0045-96584-6625", false };
	Library library;
	library.add_patron(patron1);
	library.add_patron(patron2);
	library.add_book(book1);
	library.m_vbooks[0].set_checked_in(true);
	library.add_book(book2);
	library.m_vbooks[1].set_checked_in(true);

	try
	{
		if (!is_fee_owed(patron1))
		{
			Transaction transaction1{ book1, patron1, {2017, Month::jan, 21} };
			library.add_transaction(transaction1);
			library.check_book_out(book1, patron1);
		}
		else
		{
			string s = "Patron " + patron1.get_user_name();
			error(s, ": this patron owes a fee!");
		}

		if (!is_fee_owed(patron2))
		{
			Transaction transaction2{ book2, patron2,{ 2017, Month::jan, 21 } };
			library.add_transaction(transaction2);
			library.check_book_out(book2, patron2);
		}
		else
		{
			string s = "Patron " + patron2.get_user_name();
			error(s, ": this patron owes a fee!");
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "error: " << e.what() << '\n';
	}

	if (!library.m_vtransactions.empty())
	{
		for (const auto &transaction : library.m_vtransactions)
		{
			cout << "Book " << transaction.m_book.get_title() << " has been checked "
				<< "out by " << transaction.m_patron.get_user_name() << '\n';
		}
	}
	else
	{
		cout << "No books were checked out, either because no users wanted to do so, "
			<< "or because they all owe fees and so no transactions could be made.\n";
	}

	if (!library.fee_owed_patrons().empty())
	{
		cout << "The following patron(s) owe(s) fees:\n";
		for (const auto x : library.fee_owed_patrons())
		{
			cout << x.get_user_name() << '\n';
		}
	}
	else
	{
		cout << "No patrons owe fees.\n";
	}

	if (!library.m_vbooks.empty())
	{
		cout << "\nInformation on all the books in the library (currently just two):\n";
		for (const auto &book : library.m_vbooks)
		{
			print_book_info(book);
		}
	}
}


I can't show anything else because it's going to make the post exceed the max limit.
IMO test() should not be part of any of your classes. The idea behind the test() function is to "test" the class interface, making it a friend means you can't effectively test the class access specifiers.

You're using exceptions for your error checking, why is your library and transactions not inside try catch blocks?

Do you realize that the following is using the class copy constructor?
1
2
3
4
5
6
Book book1;
	Book book2;
	try
	{
		book1 = { "MyBook", "William Copperfield", "5-1-2-X",
		{ 2001, Month::apr, 25 }, false, Genre::fiction };

While it will probably do what you want, in this case, you really should be creating the instances of the variables inside the try blocks. This means, of course, that you'll need to create "different" instances of your books in all of the try/catch blocks. Remember that this test() function is trying to test all possible problems in your class designs, not be a final program and you should be testing all possible failure modes.

EDIT: Also your "failures" should be documented. And you should be checking every possible failure mode separately. Ie, you should have a test for a bad ISBN and another for a bad Date, etc.

Last edited on
I'm not sure how I should handle the stuff with the Library class' member vectors in the test() function if I don't make it a friend of the class. Should I write getters for the vectors so I can access them from the class interface, or would that be a bad idea? I know that users have no business knowing that the library is using vectors to hold the data, but how else will I look into the transactions vector to find what patron checked out what book, for example, when I'm in the test() function?

I already tested most of the stuff on my end, so I know it's fine. I just need to test the error handling for situations where two or more books have the same ISBN (I already saw what happens when I try to make an ISBN with '.' characters or ' ' (space) characters).

I used a copy constructor for the books because I didn't want to create separate instances of the "same" book. It felt weird. And I don't any way of checking for a "bad" transaction or patron. I guess I could give an error if there are two patrons with the same library card number, though? But yeah, it's because there's no exception being thrown by the Library, Patron and Transaction constructors that I didn't put them in try-catch blocks by themselves.
Last edited on
I already tested most of the stuff on my end, so I know it's fine. I just need to test the error handling for situations where two or more books have the same ISBN (I already saw what happens when I try to make an ISBN with '.' characters or ' ' (space) characters).

So then why did you remove this code? The sole purpose of this "test" function is to show that the whole class interface is working correctly. You should leave all the test cases intact and just add more test cases. And a big part of the function is the documentation. Every "test" should be documented as to what it is testing and whether or not the "test" should fail or pass.

I used a copy constructor for the books because I didn't want to create separate instances of the "same" book. It felt weird.

It's not "weird" that's the point of the test function, to test the classes and their all of the interfaces. If your classes actually had the copy constructor and assignment operators you would need to individually check those operations as well as the constructors. You should also be checking all of your getter/setter functions as well. Your "test" function will probably have a lot of code duplication because of the use of the exceptions, but that is expected in this type of function.

I'm not sure how I should handle the stuff with the Library class' member vectors in the test() function if I don't make it a friend of the class.

This is something you need to figure out. Just throwing everything to a "friend" breaks all encapsulations you have been working so hard at. This is part of the design, you need to design the interfaces to these elements. To do this you need to ask yourself what is the purpose of all the elements in the class, and the who (needs access) where (do you allow this access) and why does someone need access. For example should patron X have access to patron Y's information? Should patron X or patron Y be able to see what books are out to some other person, or should they just be able to see that the Libraries copy of some book is not available?



Here's test() as it is now:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
void test()
{
	// test1: test for invalid date
	// should fail and give error message about invalid copyright date
	// checked a "0" month, a negative day, and also a negative year
	// all failed with expected error message
	try
	{
		Book book{ "MyBook", "Some Author", "5-9-8-X", 
		{ -2017, Month::apr, 4 }, false, Genre::fiction };
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.1: error: " << e.what() << '\n';
		cin.clear();
	}
	catch (Date::Invalid)
	{
		cerr << "try-catch block no.1 error: copyright date for one or more books is invalid\n";
	}
	
	// test 2: test for invalid ISBN
	// should fail and give error message about invalid ISBN
	// have tested letters, spaces, and '.' characters
	// all failed with error message as they should have
	try
	{
		Book book2{ "MyBook", "Some Author", "b-a-d-7",
		{ 2017, Month::jan, 4 }, false, Genre::fiction };
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.2 error: " << e.what() << '\n';
		cin.clear();
	}
	catch (Date::Invalid)
	{
		cerr << "try-catch block no.2 error: copyright date for one or more books is invalid\n";
	}

	// test 3: this is a test for two or more books with the same ISBN, 
	// so everything has to be good except for two ISBNs being the same
	try
	{
		
		Book book3{ "Dummy", "William Copperfield", "5-6-9-4", 
		{2015, Month::apr, 25}, false, Genre::biography };
		
		Library library;
		library.add_book(book3);
		book3.set_checked_in(true);

		cout << "In test-case 3:\n";
		if (library.book_in_library(book3))
		{
			for (unsigned i = 0; i < library.get_books_v().size(); ++i)
			{
				cout << "Book " << library.get_books_v()[i].get_title()
					<< " is in library.\n";
			}
		}
		else
		{
			cout << "Book was not added to library.\n";
		}
		
		Book book4{ "About Me", "Osman Zakir", "5-6-9-4",
		{2015, Month::apr, 17}, false, Genre::biography };

		for (const auto &book : library.get_books_v())
		{
			if (book4 == book)
			{
				error("this book has the same ISBN as a book that's already in the library");
			}
			else
			{
				library.add_book(book4);
				book4.set_checked_in(true);
				break;
			}
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.3 error: " << e.what() << '\n';
	}
	catch (Date::Invalid)
	{
		cerr << "try-catch block no.3 error: copyright date for one or more books is invalid\n";
	}

	// test 4: test to check if two or more patrons with the same library card number
	// will trigger an error or not
	// should fail with error message as expected
	try
	{
		Patron patron1{ "Osman Zakir", "1234565165548", false };
		Patron patron2{ "Some Guy", "1234565165548", false };

		Library library;
		library.add_patron(patron1);

		for (const auto &patron : library.get_patrons_v())
		{
			if (patron2 == patron)
			{
				error("this patron has the same library card number as another patron in the library");
			}
			else
			{
				library.add_patron(patron2);
				break;
			}
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.4 error: " << e.what() << '\n';
	}

	// test 5: testing a situation where a patron who owes a fee
	// tries to check out a book
	// should fail with error message as expected
	try
	{
		Patron patron3{ "John Doe", "1239752925379", true };

		Library library;
		library.add_patron(patron3);

		if (!is_fee_owed(patron3))
		{
			for (unsigned i = 0; i < library.get_books_v().size(); ++i)
			{
				if (!library.get_books_v().empty() && library.get_books_v().size() == 1)
				{
					Transaction transaction{ library.get_books_v()[i], 
						patron3, {2017, Month::jan, 22} };
					library.add_transaction(transaction);
					break;
				}
			}
		}
		else
		{
			error("this patron owes a fee!");
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.5 error: " << e.what() << '\n';
	}

	// test 6: test for an invalid library card number
	// should fail with error message as expected
	try
	{
		Patron patron{ "A", "12345badandwontwork", false };
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.6 error: " << e.what() << '\n';
	}

	// test 7: test for a situation where a patron tries to check out a book
	// that exists but isn't in the library
	// should fail with expected error message
	try
	{
		Book book5{ "A Book", "Some Author", "5-6-1-8",
		{2016, Month::mar, 12}, false, Genre::nonfiction };

		Patron patron{ "B", "1234512345679", false };
		Library library;
		library.add_patron(patron);

		// note: not adding book to library here
		// patron will try to check out that book:
		if (!is_fee_owed(patron))
		{
			if (library.book_in_library(book5))
			{
				library.check_book_out(book5, patron);
				Transaction transaction{ book5, patron, {2017, Month::apr, 25} };
				library.add_transaction(transaction);
			}
			else
			{
				error("book not in library!");
			}
		}
		else
		{
			error("this patron owes a fee!");
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.7 error: " << e.what() << '\n';
	}

	// test 8: testing a situation where a patron who isn't in the library
	// tries to check out a book
	try
	{
		Library library;
		Patron patron1{ "Some Patron", "1234596875639", false };

		Book book1{ "Some Book", "A", "1-2-3-9",
		{2017, Month::aug, 14}, false, Genre::fiction };

		library.add_book(book1);
		book1.set_checked_in(true);

		if (library.patron_in_library(patron1) && !is_fee_owed(patron1))
		{
			if (library.book_in_library(book1))
			{
				library.check_book_out(book1, patron1);
				Transaction trans{ book1, patron1, {2017, Month::mar, 25} };
				library.add_transaction(trans);
			}
			else
			{
				string s = "book " + book1.get_title();
				error(s, " not in library!");
			}
		}
		else if (!library.patron_in_library(patron1))
		{
			string s = "book " + patron1.get_user_name();
			error(s, " not in library!");
		}
		else if (is_fee_owed(patron1))
		{
			string s = "book " + patron1.get_user_name();
			error(s, " owes a fee!");
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.8 error: " << e.what() << '\n';
	}

	// testing a situation where there's no problem
	// should pass with everything working
	// result: test failed because library's books'
	// vector came out empty
	try
	{
		Library library;
		Patron patron{ "Some Patron", "5694125968756", false };
		library.add_patron(patron);

		if (!library.get_books_v().empty())
		{
			const string isbn = "5-6-9-4";
			for (auto book : library.get_books_v())
			{
				if (book.get_isbn() == isbn)
				{
					if (library.patron_in_library(patron) && !is_fee_owed(patron))
					{
						library.check_book_out(book, patron);
						Transaction trans{ book, patron, {2017, Month::jan, 23} };
						library.add_transaction(trans);
					}
					else if (!library.patron_in_library(patron))
					{
						string s = "patron " + patron.get_user_name();
						error(s, " not in library!");
					}
					else if (is_fee_owed(patron))
					{
						string s = "patron " + patron.get_user_name();
						error(s, " owes a fee!");
					}
				}
			}
		}
		else
		{
			error("no books in the library!");
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "try-catch block no.9 error: " << e.what() << '\n';
	}

	Library lib;
	if (!lib.get_transactions_v().empty())
	{
		for (auto transaction : lib.get_transactions_v())
		{
			cout << "Book " << transaction.m_book.get_title() << " has been checked out "
				<< "by " << transaction.m_patron.get_user_name() << ".\n";
		}
	}
	else
	{
		cout << "No transactions have been made successfully.\n";
	}
}

Please help me figure out why test no.9 isn't working right. Thanks in advance.

Functions for adding book, patron and transaction to library:
1
2
3
void add_book(const Book &book) { m_vbooks.push_back(book); }
	void add_patron(const Patron &patron) { m_vpatrons.push_back(patron); }
	void add_transaction(const Transaction &transaction) { m_vtransactions.push_back(transaction); }


Pages: 12