Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

Done in C++, follow along. You will need to have the following #include files (and these are the only #include files you can have in

Done in C++, follow along.

image text in transcribedimage text in transcribedimage text in transcribedimage text in transcribedimage text in transcribedimage text in transcribedimage text in transcribedimage text in transcribed

image text in transcribedimage text in transcribedimage text in transcribedimage text in transcribedimage text in transcribedimage text in transcribedimage text in transcribed

You will need to have the following #include files (and these are the only #include files you can have in your assignment), so write this at the top of a03.cxx: #include // for regular expression support #include #include // for std: :make-shared and std:: shared-ptr #include #include // for C's strcmp (see below) #include // for std: :optional #include #include is a type that internally stores a reference-counted pointer to an instance of program_input Recall: Object-oriented programming languages require their objects to be references and the objects are typically dynamically allocated. Objects by default in C++ are values and are statically- allocated. By using std::shared_ptr you will be dynamically allocating a "reference" to the object's instances. In addition, by using std::shared ptr's facilities you will not be burdened with having to manually call new to allocate memory, manually calling delete to free the memory, and write code to properly handle exceptions if/when such occurs. This not only simplifies writing the code, it also helps ensure your program will have no memory leaks. (When destroyed std:shared ptr's will free any memory associated with the pointer they refer to when destroying the last std::shared ptr refering to that memory location.) You will need to write the read all inputs0 function later, for now, write this stub in to your program so you can call it (even though it will do nothing until you write the code for it later): std:vector read all_inputs(all_inputs type& /*ai*/) NOTE: For now the ai function argument is commented out to avoid a compiler warning about ai being unused. return 0:// i.., return a default constructed std:.vector main): Processing Command Line Arguments Before writing derived-from-program_input classes, let's first deal with processing the command line arguments passed to main). The final program's command line argument processing must output to standard error these messages when invalid command line inputs are provided, e.g $ ./a03.exe Usage: ./a03.exe -d $ ./a03.exe junk Usage: ./a03.exe -d NOTE: Invalid argument (s) provided. $ ./a03.exe -id Usage: ./a03.exe -d NOTE: The -d option appears without a argument i.e., only when the program is used with the -d argument and is also provided a directory, will the program perform some meaningful processing Recall that main()'s argv[0] value is the name of the program, i.e., "./a03.exe" in the examples above) Since you will need to output the above "Usage" line multiple times, write such as a function, i.e std::ostream& output_usage(std: :ostream& os, int /argc*/, char argv[]) oS "; return os; ASIDE: I have provided this function to you (i) as an example, and (ii) to remind/encourage you to write functions to simplify code rather than copying-and-pasting code all over the place! Notice this function returns the stream passed to it. This allows you to write code like this: output_message(std::cerr, argc, argv) >and ostream& operator facilities are defined in the "filesystem" namespace that is contained within the "std" namespace. Writing "std:filesystem::" is a bit much. Since we have "using namespace std;" one would only need to write "filesystem::" but even that is long. Nicely C++ allows programmers to create an alias for any namespace. Just as typedefs and type aliases do NOT create new types, namespace aliases don't create new namespaces! In this case, the alias "fs" is defined to refer to "std::filesystem". This will allow you to access names in the filesystem portion of the C+ Standard Library by prefixing them with "fs::". ASIDE 2: You might ask why not write "using namespace std; using namespace std:filesystem;"? While one could do that, the more symbol names you allow to be automatically searched the more likely there will be name clashes/ambiguities (i.e., two symbols having the same name). When such occurs, the only way to resolve it is to write the namespace in front of that symbol. It is better to avoid using too many "using namespace" directives in code.:-) Recall that argc will always be at least one, i.e., when argc1 argv's first index only has the program name. Thus, write the code that checks if argc =-1 and if it is, invoke outputusage(cerr, argc, argv); and then return 1 from main (i.e., return an exit status of 1 to the calling program). Earlier you could see the only valid command line input to the program is the path name of a directory You will store this directory name as a std::string object EXCEPT you want to know whether or not a directory was actually read in. In the past, you might have declared a bool variable to record whether or not the variable was set to a meaningful value. Fortunately in C++17 (and one can use the open source Boost.Optional library in earlier versions of C++) there is a class called std::optionalkT>. The purpose of std:optionalkT> is to know whether or not it is storing an instance of T --and it does this without dynamic memory allocation.: So to know whether or not a directory was given to the program, declare a scan_directory variable as follows: optional argument.n" following it. o Then return 2 from main() (i.e., return with an exit status of 2 to the calling process). Otherwise: Increment args-pos (i.e., you need to skip the argument). o Assign scan_directory to argvll index that holds the string the user provided. Write "continue;" to continue iterating the for loop --or use "else" with the "-d" if statement block below. Otherwise, if the current argvl string doesn't match "-d" then: Invoke output usage(cerr, argc, argv) and additionally output "tNOTE: Invalid argument(s) provided.In" to std::cerr. ASIDE: Notice how the structure of this for loop and its if statements elegantly "consume" the command line arguments. In future programs, consider writing your command line argument processing in this manner. :-) IMPORTANT: If you've not already done so, compile your code! Make sure you code can process the command line arguments properly! Add additional cout/cerr/printf statements to verify that everything is working. (Delete any additional debugging statements you add before submitting your assignment though!) main): Recursively Navigating A Directory Tree Don't panic! This is very easy to do in C++17 (or in earlier versions of C++using the open source Boost.Filesystem library)! First let's declare a variable of type "all inputs_type" we defined earlier: all-inputs-type all-inputs; // this variable wi1 hold all discovered program input files The C++17 portion of the standard library has two very easy-to-use iterator objects: std:filesystem::directory iterator std::filesystem:recursive_directory_iterator The "begin" iterator is created by passing the directory path (e.g., a std:string) of the directory one wants to search in/from. The "end" iterator is created by default constructing the iterator. The C++ standard defines begin) and end) properly for these iterators so one can actually use a range-based for loop to iterate through the directory entries involved, i.e., write this in you program next for (auto& entry: fs::recursive_directory_iterator(scan_directory.value())) cout can be found in these locations: Boost.Filesystem: https://www.boost.org/doc/libs/1_69 0/libs/filesystem/doc/index.htm o The "Tutorial" page has a number of useful examples. After reading such the reference pages can be scanned (and will make sense). cppreference.com's page: https://en.cppreference.com/w/cpp/header/filesystem main): Determining If The Filename Has The Pattern "yobYYYY.txt" Although you could write code to determine if a filename has the pattern "yobYYYY.txt", that code would likely be more complicated than simply using the portion of the standard library! So the following instructions walk you through doing this. Nicely this will not only determine if the file name has the correct pattern, but, this will extract the value of YYYY into an integer variable as wel If you've not already, then read Josuttis chapter 14 on Regular Expressions. Look at the examples and what is output. The notation that can be used in regular expressions is detailed in 14.8. Since you know the current "entry" in the loop is a regular file, write the following code in your for loop: // determine if file anme is of the form yobYYYY.txt... static regex const baby_name_file_regex(R" (yob(ldf4).txt)"; smatch mr; // variable to hold regex matched results string const fnameentry.path).filename) // only match the filename if (regex_match(fname, mr, baby_name_file_regex)) // regex_match() only returns true when the match is successful // you will be writing code in here below // for now use the following cout to see that it actually matches... cout . shared_ptr's store pointers to the type specified in its template parameter. Since yob_baby_name_file inherits from program_input, a yob baby_name_file* can be assigned to a program_input* (i.e., implicit upcasting). The call to make_shared ensures yob_baby_name_file is dynamically allocated and returns a std::shared_ptr. The latter is then passed to the constructor of a std:shared ptr (as that is what is stored in the all_input_type vector!) which simply mplicitly upcasts the derived pointer to its base class properly adjusting the reference counters appropriately When the statement finishes the temporary shared_ptr is destroyed but it is no longer the only instance pointing to the memory that was allocated (because it was added to the vector) so the memory is not freed. Only when the last instance (ie, when the all-input vector is destroyed) does the memory associated with the various program input-derived instances get destroyed Last Step: Writing The Code For read_all_inputs0 Earlier you were given a trivial code stub for the read_all_inputs() function (which is called in the last line in main). Now that you are actually populating the all_inputs variable with data, it makes sense to write the code for this function! Do the following in the order listed: Declare a variable called "retval" of type std::vectorbl function.) Invoke std.vector's "reserve()" function passing in aisize(). o ai is the argument passed to read_all_inputs (see earlier stub) o The reserve calls tells the vector to ensure enough memory has been dynamically allocated to This will be the returned from the . hold ai.size) elements. Use a range for loop to iterate over all elements in ai (i.e., the argument passed to the function) and within the loop: o ASIDE: Below "" is used to refer to the current element involved with the for loop iteration. Write try block with the single line of code: Invoke the push _back() function on retval and pass what i->read) returns. -ASIDE: i is an instance of std::shared_ptr. The ->is the indirection operator which allows one to invoke the read() function on the internally stored program_input* object. o In the catch(...) handler write the single line of code: Call the push_back() function on retval passing in false. ASIDE: Since reserve() was used to pre-allocate all memory, push_back() should not throw. Even if push_back throws, the C++ Standard guarantees that push back) has no effect if something is thrown from it. Thus, if an exception is thrown, then it must have come from std:shared ptr or read0. Either way, you want the code to continue processing recording exactly one bool for each element in ai. Thus, this catch(...) handler calls push_back(false) to ensure there is a (false) bool added to ai when an exception is thrown. Finally, return std::move(retval); You will need to have the following #include files (and these are the only #include files you can have in your assignment), so write this at the top of a03.cxx: #include // for regular expression support #include #include // for std: :make-shared and std:: shared-ptr #include #include // for C's strcmp (see below) #include // for std: :optional #include #include is a type that internally stores a reference-counted pointer to an instance of program_input Recall: Object-oriented programming languages require their objects to be references and the objects are typically dynamically allocated. Objects by default in C++ are values and are statically- allocated. By using std::shared_ptr you will be dynamically allocating a "reference" to the object's instances. In addition, by using std::shared ptr's facilities you will not be burdened with having to manually call new to allocate memory, manually calling delete to free the memory, and write code to properly handle exceptions if/when such occurs. This not only simplifies writing the code, it also helps ensure your program will have no memory leaks. (When destroyed std:shared ptr's will free any memory associated with the pointer they refer to when destroying the last std::shared ptr refering to that memory location.) You will need to write the read all inputs0 function later, for now, write this stub in to your program so you can call it (even though it will do nothing until you write the code for it later): std:vector read all_inputs(all_inputs type& /*ai*/) NOTE: For now the ai function argument is commented out to avoid a compiler warning about ai being unused. return 0:// i.., return a default constructed std:.vector main): Processing Command Line Arguments Before writing derived-from-program_input classes, let's first deal with processing the command line arguments passed to main). The final program's command line argument processing must output to standard error these messages when invalid command line inputs are provided, e.g $ ./a03.exe Usage: ./a03.exe -d $ ./a03.exe junk Usage: ./a03.exe -d NOTE: Invalid argument (s) provided. $ ./a03.exe -id Usage: ./a03.exe -d NOTE: The -d option appears without a argument i.e., only when the program is used with the -d argument and is also provided a directory, will the program perform some meaningful processing Recall that main()'s argv[0] value is the name of the program, i.e., "./a03.exe" in the examples above) Since you will need to output the above "Usage" line multiple times, write such as a function, i.e std::ostream& output_usage(std: :ostream& os, int /argc*/, char argv[]) oS "; return os; ASIDE: I have provided this function to you (i) as an example, and (ii) to remind/encourage you to write functions to simplify code rather than copying-and-pasting code all over the place! Notice this function returns the stream passed to it. This allows you to write code like this: output_message(std::cerr, argc, argv) >and ostream& operator facilities are defined in the "filesystem" namespace that is contained within the "std" namespace. Writing "std:filesystem::" is a bit much. Since we have "using namespace std;" one would only need to write "filesystem::" but even that is long. Nicely C++ allows programmers to create an alias for any namespace. Just as typedefs and type aliases do NOT create new types, namespace aliases don't create new namespaces! In this case, the alias "fs" is defined to refer to "std::filesystem". This will allow you to access names in the filesystem portion of the C+ Standard Library by prefixing them with "fs::". ASIDE 2: You might ask why not write "using namespace std; using namespace std:filesystem;"? While one could do that, the more symbol names you allow to be automatically searched the more likely there will be name clashes/ambiguities (i.e., two symbols having the same name). When such occurs, the only way to resolve it is to write the namespace in front of that symbol. It is better to avoid using too many "using namespace" directives in code.:-) Recall that argc will always be at least one, i.e., when argc1 argv's first index only has the program name. Thus, write the code that checks if argc =-1 and if it is, invoke outputusage(cerr, argc, argv); and then return 1 from main (i.e., return an exit status of 1 to the calling program). Earlier you could see the only valid command line input to the program is the path name of a directory You will store this directory name as a std::string object EXCEPT you want to know whether or not a directory was actually read in. In the past, you might have declared a bool variable to record whether or not the variable was set to a meaningful value. Fortunately in C++17 (and one can use the open source Boost.Optional library in earlier versions of C++) there is a class called std::optionalkT>. The purpose of std:optionalkT> is to know whether or not it is storing an instance of T --and it does this without dynamic memory allocation.: So to know whether or not a directory was given to the program, declare a scan_directory variable as follows: optional argument.n" following it. o Then return 2 from main() (i.e., return with an exit status of 2 to the calling process). Otherwise: Increment args-pos (i.e., you need to skip the argument). o Assign scan_directory to argvll index that holds the string the user provided. Write "continue;" to continue iterating the for loop --or use "else" with the "-d" if statement block below. Otherwise, if the current argvl string doesn't match "-d" then: Invoke output usage(cerr, argc, argv) and additionally output "tNOTE: Invalid argument(s) provided.In" to std::cerr. ASIDE: Notice how the structure of this for loop and its if statements elegantly "consume" the command line arguments. In future programs, consider writing your command line argument processing in this manner. :-) IMPORTANT: If you've not already done so, compile your code! Make sure you code can process the command line arguments properly! Add additional cout/cerr/printf statements to verify that everything is working. (Delete any additional debugging statements you add before submitting your assignment though!) main): Recursively Navigating A Directory Tree Don't panic! This is very easy to do in C++17 (or in earlier versions of C++using the open source Boost.Filesystem library)! First let's declare a variable of type "all inputs_type" we defined earlier: all-inputs-type all-inputs; // this variable wi1 hold all discovered program input files The C++17 portion of the standard library has two very easy-to-use iterator objects: std:filesystem::directory iterator std::filesystem:recursive_directory_iterator The "begin" iterator is created by passing the directory path (e.g., a std:string) of the directory one wants to search in/from. The "end" iterator is created by default constructing the iterator. The C++ standard defines begin) and end) properly for these iterators so one can actually use a range-based for loop to iterate through the directory entries involved, i.e., write this in you program next for (auto& entry: fs::recursive_directory_iterator(scan_directory.value())) cout can be found in these locations: Boost.Filesystem: https://www.boost.org/doc/libs/1_69 0/libs/filesystem/doc/index.htm o The "Tutorial" page has a number of useful examples. After reading such the reference pages can be scanned (and will make sense). cppreference.com's page: https://en.cppreference.com/w/cpp/header/filesystem main): Determining If The Filename Has The Pattern "yobYYYY.txt" Although you could write code to determine if a filename has the pattern "yobYYYY.txt", that code would likely be more complicated than simply using the portion of the standard library! So the following instructions walk you through doing this. Nicely this will not only determine if the file name has the correct pattern, but, this will extract the value of YYYY into an integer variable as wel If you've not already, then read Josuttis chapter 14 on Regular Expressions. Look at the examples and what is output. The notation that can be used in regular expressions is detailed in 14.8. Since you know the current "entry" in the loop is a regular file, write the following code in your for loop: // determine if file anme is of the form yobYYYY.txt... static regex const baby_name_file_regex(R" (yob(ldf4).txt)"; smatch mr; // variable to hold regex matched results string const fnameentry.path).filename) // only match the filename if (regex_match(fname, mr, baby_name_file_regex)) // regex_match() only returns true when the match is successful // you will be writing code in here below // for now use the following cout to see that it actually matches... cout . shared_ptr's store pointers to the type specified in its template parameter. Since yob_baby_name_file inherits from program_input, a yob baby_name_file* can be assigned to a program_input* (i.e., implicit upcasting). The call to make_shared ensures yob_baby_name_file is dynamically allocated and returns a std::shared_ptr. The latter is then passed to the constructor of a std:shared ptr (as that is what is stored in the all_input_type vector!) which simply mplicitly upcasts the derived pointer to its base class properly adjusting the reference counters appropriately When the statement finishes the temporary shared_ptr is destroyed but it is no longer the only instance pointing to the memory that was allocated (because it was added to the vector) so the memory is not freed. Only when the last instance (ie, when the all-input vector is destroyed) does the memory associated with the various program input-derived instances get destroyed Last Step: Writing The Code For read_all_inputs0 Earlier you were given a trivial code stub for the read_all_inputs() function (which is called in the last line in main). Now that you are actually populating the all_inputs variable with data, it makes sense to write the code for this function! Do the following in the order listed: Declare a variable called "retval" of type std::vectorbl function.) Invoke std.vector's "reserve()" function passing in aisize(). o ai is the argument passed to read_all_inputs (see earlier stub) o The reserve calls tells the vector to ensure enough memory has been dynamically allocated to This will be the returned from the . hold ai.size) elements. Use a range for loop to iterate over all elements in ai (i.e., the argument passed to the function) and within the loop: o ASIDE: Below "" is used to refer to the current element involved with the for loop iteration. Write try block with the single line of code: Invoke the push _back() function on retval and pass what i->read) returns. -ASIDE: i is an instance of std::shared_ptr. The ->is the indirection operator which allows one to invoke the read() function on the internally stored program_input* object. o In the catch(...) handler write the single line of code: Call the push_back() function on retval passing in false. ASIDE: Since reserve() was used to pre-allocate all memory, push_back() should not throw. Even if push_back throws, the C++ Standard guarantees that push back) has no effect if something is thrown from it. Thus, if an exception is thrown, then it must have come from std:shared ptr or read0. Either way, you want the code to continue processing recording exactly one bool for each element in ai. Thus, this catch(...) handler calls push_back(false) to ensure there is a (false) bool added to ai when an exception is thrown. Finally, return std::move(retval)

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access with AI-Powered Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Students also viewed these Databases questions