Lab 02 - S.N.A.P. (01/04/2022)

References

Files

No supplied source code.

Description

"Class design is type design. Defining effective classes can be challenging. For this lab you are to design classes for a school database with has-a and is-a relationships that have natural syntax, intuitive semantics, and efficient memory allocation. The class objects are populated from parsed input strings. Use object inheritance, polymorphism, and function and operator overloading where needed. All member data and internal functions are private. All classes have a "toString" method with friends for the insertion ("<<") operator."

Learning Outcomes

By completing the SNAP Lab, you will be able to:


Classes

Object Oriented Programming (OOP) vs. Procedural Programming.

  1. Division Of Program: In procedural programming a program is divided into small functions while in OOP it is divided into objects.
  2. Importance: In procedural programming importance is not given to data but to procedures. In OOP importance is given to data rather than functions, because OOP works with real world objects.
  3. Approach: Procedural programming is top down (design functions first.) OOP is bottom up (design objects then interactions.)
  4. Access Specifiers: In procedural programming no access specifiers are used. In OOP different type of specifiers are used for different levels of data protection.
  5. Data Moving: In procedural programming, data can move freely from function to function. In OOP, data can only move via member functions.
  6. Data Access: In procedural programming global data is acceptable and freely accessible. In OOP, public or private access specifiers are used to control data access.
  7. Data Hiding: In procedural programming, no particular method is used for data hiding and therefore it is less secure. A particular method is used for data hiding in OOP so it is more security.
  8. Overloading: Overloading is not possible in procedural programming. Overloading is possible in OOP in the form of function and operator overloading.

All the above are key differences between procedural and object oriented programming. Examples of procedure oriented languages are C, VB, and Fortran. Examples of object oriented languages are C++, Java, C#, and Python.

Class Design.

What makes for good class design? Here are a few pointers:

  1. Choose simple over complex design.
  2. Choose flat hierarchies - avoid deep hierarchies.
  3. Encapsulate - hide complexity behind a simpler interface. Keep as much of your object hidden from both the prying eyes and sticky fingers of the outside world.
  4. Abstract - hide as much of the inner workings of you object from the simple minds of the code that needs to use your objects.
  5. Use inheritance to take advantage of commonality.
  6. Use polymorphism (also called overloading) to perform a single action in different ways dependent on the data type (such as output).
  7. Avoid as much duplication of data as possible.
  8. Changing one object should have little (if any) effect on other objects.
  9. Remember:
    • K.I.S.S. (Keep It Simple Stupid) - most systems work best if they are kept simple rather than made complicated; therefore simplicity should be a key goal in design and unnecessary complexity should be avoided.
    • D.R.Y. (Don't repeat yourself) - a software development principle aimed at reducing repetition of software patterns, replacing them with abstractions; and several copies of the same data, using data normalization to avoid redundancy.
    • Y.A.G.N.I. (You Aren't Gonna Need It) - do not add functionality until deemed necessary.
  10. Finally, practice, practice, practice!

Constructors, Destructors, and Assignment Operators.

Every class will have one or more constructors, a destructor, and a copy assignment operator. These are the bread-and-butter functions, the ones that control the fundamental operations of bringing a new object into existence and making sure it's initialized, getting rid of an object and making sure it's properly cleaned up, and giving an object a new value. Making mistakes in these functions will lead to far-reaching (and unpleasant) repercussions throughout your classes, so it's vital that you get them right.

A destructor is a special member function that is called when the lifetime of an object ends. The purpose of the destructor is to free the resources that the object may have acquired during its lifetime.

Here are some general rules for constructors/destructors:

  1. Compilers generate default copy constructors, copy assignment operators, and destructors only when they are not user defined.
  2. Compilers won't generate nor override user declared constructors.
  3. Polymorphic base classes (base classes that manipulate derived class types through base case interfaces) should always declare virtual destructors. Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors.
  4. C++ does not like destructors that emit exceptions. (Incomplete destruction of an object is disastrous!)

C++ Member Functions.

Member functions allow programs to interact with the data contained within a user defined data types (classes and structs). Creating user defined types provide "divide and conquer" flexibility - one programmer declares the data type interface while another programmer uses the interface declaration to implement the access methods to the data type data. The two pieces are put together and compiled for usage. User defined types provide encapsulation defined in the Object Oriented Programming (OOP) paradigm.

Within classes, to protect the data members, the programmer can define functions to perform the operations on those data members. Member functions and functions are names used interchangeably in reference to classes. Function prototypes are declared within the class definition and can be defined within the class definition when small (10 lines or less). However, most functions can have very large definitions and make the class very unreadable. Therefore, with large member functions (greater than 10 lines), the definition of the function is placed outside of the class definition using the scope resolution operator "::". The scope resolution operator allows a programmer to define the functions somewhere else allowing the programmer to provide a header file (.h) defining the class and a .obj file built from the compiled .cpp file which contains the function definitions. As such, hiding the implementation can prevent tampering. The user would have to define every function again to change the implementation. Functions within classes can access and modify (unless the function is constant) data members without declaring them, because the data members are already declared in the class.

C++ Interface.

An interface is implemented with a pure abstract class in C++. An interface class should contain only pure virtual public methods and static const data. e.g.

Private vs Protected Class Data Members.

According to The C++ Programming Language (3rd. Ed.) by Bjarne Stroustrup on page 405: "Members declared protected are far more open to abuse than members declared private. In particular, declaring data members protected is usually a design error. Placing significant amounts of data in a common class for all derived classes to use, leaves that data open to corruption. Worse, protected data, like public data, cannot easily be restructured because there is no good way of finding every use. Thus, protected data becomes a software maintenance problem."

So, in the end, when choosing between protected or private, be prepared to answer the following questions: How much trust are you willing to put into the programmer of the derived class? Is the cost of additional maintenance when using protected (class global) members worth more than the obvious alternative private design? By default, assume the derived class is not to be trusted, and make your members private. If you have a very good reason to give free access of the base class' internals to its derived classes, then, well ... just go ahead and make class members private and use public getters/setters.


The Lab

Lab requirements:

  1. Design classes for snap, csg, cdh, and cr. Create an UML diagram of your classes (to be submitted with your source files). (See B-Y-UML.pdf.)
  2. Write your own main() function to read and process lab input files.
    • Use argv[1] to select the input file
    • and argv[2] to select the output file.
  3. Add default and other constructors to each class. (A default constructor is called with no arguments - either defined with an empty parameter list, or with default arguments provided for every parameter. If no user-declared constructors of any kind is provided for a class type (struct, class, or union), the compiler will always declare a default constructor as an in-line public member of its class.)
  4. Use at least one instance of class inheritance.
  5. Use polymorphism to add a toString() method with a friend insertion operator (<<) to every derived class.
  6. Read from the input file the following types of input strings, instantiate and populate the corresponding class objects, and store (push_back) in class template vectors:

    1. snap(studentID,studentName,studentAddress,studentPhone).
    2. csg(courseName,studentID,studentGrade).
    3. cdh(courseName,day,time).
    4. cr(courseName,room).

  7. Use a try-catch construct to handle erroneous input strings. (See C++ Exceptions.)
  8. Using the input strings and class vectors, output the four lists in the following order:

    1. Input Strings: Output input lines as they are read from the input file, preceding the line with "**Error: " for recognizable type. (List in input order.)
    2. Vectors: Output the contents of all vectors as shown in the example output files and requirements. (List in vector push_back order - not sorted.)
    3. Course Grades: For each course, output all student names and grade. (List in csg vector order.)
    4. Student Schedules: For each student, output student name header followed by all student courses. Where a course is taught on multiple days, combine and output a single day string (ie., combine T and Th into TTh.) (List students in snap vector order and courses in csg vector order.)

Finally...

Lab design:

When designing classes (objects) consider:

  1. Inheriting commonality.
  2. Avoid as much duplication of data as possible.
  3. Changing one object has little (if any) effect on other objects.

Design Snap, Csg, Cdh, Cr and Course classes such that:

  1. Snap has-a studentID, studentName, studentAddress, and studentPhone.
  2. Csg is-a Course and has-a studentID and studentGrade.
  3. Cdh is-a Course and has-a day and time.
  4. Cr is-a Course and has-a room.
  5. Course has-a courseName.

Your lab must use at least one example of public inheritance (is-a) and polymorphism, and overriding functions. Classes are populated from input strings.


Helps and Hints

Output Class Contents.(collapse)

  1. Example of a "toString" method with friend insertion operator:
    string toString() const
    {
       stringstream out;
       out << "Name = " << getName();
       return out.str();
    }
    
    friend std::ostream& operator<< (ostream& os, const MyClass& myclass)
    {
       os << myclass.toString();
       return os;
    }
  2. Use the insertion operator to output class contents using friend insertion operator:
    cout << endl << "Vectors:" << endl;
    for (unsigned int i = 0; i < myClasses.size(); i++)
    {
       cout <<< myClasses[i] << endl;
    }

Console/File Output Object.(collapse)

  1. C++ uses a convenient abstraction called streams to perform input and output operations in sequential media such as the screen, the keyboard or a file. A stream is an entity where a program can either insert or extract characters.

    Output from the SNAP lab is to be "streamed" to a file (as named in the command line variable argv[2].) For debug purposes, it would be convenient to switch between the file stream and cout, the standard output for the screen.

    The following code segment switches output between cout and the output file by toggling the preprocessor CONSOLE constant:

    #define CONSOLE 1
    
    cout << endl << "Input File: " << argv[1];
    ifstream in(argv[1]);
    
    cout << endl << "Output File: " << (CONSOLE ? "cout" : argv[2]);
    ostream& out = CONSOLE ? cout : *(new ofstream(tests[TEST_NUM].output));
    
  2. All output is directed to the out stream object:
    out << "Input Strings:";
    for (string line; getline(in, line);)  // Pass through all lines of code 
    {
       if (line.size() == 0) continue;
       out << endl << line;
       // ...
    }

Detecting Memory Leaks.(collapse)

  1. Place the following code near the beginning of the file containing your main function:

    #ifdef _MSC_VER
    #define _CRTDBG_MAP_ALLOC  
    #include <crtdbg.h>
    #define VS_MEM_CHECK _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    #else
    #define VS_MEM_CHECK
    #endif

  2. Put the following C pre-processor macro at the beginning of your main code:

    int main(int argc, char * argv[])
    {
       VS_MEM_CHECK               // enable memory leak check
       // Your program...
       return 0;
    }

  3. If you get something like the following when your program exits, YOU HAVE A MEMORY LEAK! You are required to remove all memory leaks.

    The thread 0x34bc has exited with code 0 (0x0).
    Detected memory leaks!
    Dumping objects ->
    {149} normal block at 0x00365008, 4000 bytes long.
     Data: <                > 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 
    Object dump complete.
    The program '[4984] Lab 02 - snap.exe' has exited with code 0 (0x0).
    

Understanding Valgrind Output.(collapse)

  • You can find helpful hints and explanations of Valgrind output here.

S.N.A.P. Grading Criteria

Instructions for submitting your lab:

Use the following test input and resulting output files in testing your SNAP lab.

Input FileOutput File
Test #1 lab02_in_01.txt lab02_out_01.txt
Test #2 lab02_in_02.txt lab02_out_02.txt
Test #3 lab02_in_03.txt lab02_out_03.txt

 

Given the following input file:

snap(12345,Charlie Brown,Manager,555-1234).
snap(67890,Lucy,Right Field,555-5678).
csg(CS101,12345,A).
csg(CS101,67890,B).
csgs(CS101,67890,B).
cdh(CS101,M,9AM).
cdh(CS101,W,9AM).
cr(CS101,1170 TMCB).

the auto-grader will be expecting to find in your output files the following:

Fail
Pass
Score = 0

 

The lab source will be peer reviewed using the following rubric:

No
Partial
Yes
Score = 0
Overall
Rating
Easy to follow, readable, organized, and well commented.
                      
***NOTE: Any attempt to circumvent any lab requirement (ie, hard coding
output, using loops instead of iterators where required, modifying a class interface, etc.) will be reported by peer reviewers and result in a zero score for the lab.
Total Score = 0