Hi all,

I’ve been given told to convert a standard C++ program into an object-oriented one, using the principals of polymorphism, encapsulation and inheritance. Our lecturer covered all three principals in one 45 minute slot. I’m completely stuck and would be grateful for any help.

The standard C++ program accepts data values and filter values from the user. It then multiplies these values together using a simple algorithm and prints the output to the screen. The standard program is shown below.

I haven’t made much of a start as I’m unsure how to allocate the classes. I’ve allocated one class to the TheFilter structure with its associated data and functions, and another to the TheData structure.

I hate to admit defeat, but I’ve never programmed using an object-oriented approach and I’ve only been studying C++ for a couple of months. Please help!

Thanks in advance.

Zeke

Standard C++ program follows:

// Purpose
// A program to demonstrate the application of a simple digital filter
// 
// Overview
// A sequence of data items and digital filter values need to be entered by the
// user. The application of the filter to the data involves a simple convolution 
// operation. The filtered data are stored separately. 
//
// Example
//  before filtering: 
//   data_in = [0 1 3 6 3 1 0]
//   filter = [-0.5 1 -0.5]
//  after filtering:
//   data_out = [-0.5 -0.5 3 -0.5 -0.5]
//  where
//   data_out[0]=data_in[0]*filter[0]+data_in[1]*filter[1]+data_in[2]*filter[2]
//   data_out[1]=data_in[1]*filter[0]+data_in[2]*filter[1]+data_in[3]*filter[2]
//   data_out[2]=data_in[2]*filter[0]+data_in[3]*filter[1]+data_in[4]*filter[2]
//   data_out[3]=data_in[3]*filter[0]+data_in[4]*filter[1]+data_in[5]*filter[2]
//   data_out[4]=data_in[4]*filter[0]+data_in[5]*filter[1]+data_in[6]*filter[2]
//
// The program checks the following
// 1. The data and filter values must have been entered before the filter is 
//    applied
// 2. The filter is not applied if the number of filter values is greater than
//    the number of input data values
// 3. The data and filter values must have been entered and the filter applied 
//    before the filtered data can be displayed
#include <iostream>
using namespace std;
 
// the data values and the filter
struct TheFilter {
  double* Values;   // the filter values
  unsigned long Length;  // number of filter values
  bool Valid;   // true if the filter values have been Valid by the user
};

struct TheData {
  double* Values;  // holds the data to be filtered
  unsigned long Length;  // number of data values
  bool Valid;   // true if the data values have been Valid by the user
};

// function return values
enum {OK,FILTER_TOO_LONG};

// function prototypes
void EnterData(TheData&);
void EnterFilter(TheFilter&);
int ApplyFilter(TheFilter, TheData, TheData&);
void DisplayData(TheFilter, TheData, TheData);
 
// Control the principal operations of the program
// Arguments: None
// Returns: 0 on completion
int main()
{
  // define the filter and its initial values
  TheFilter Filter = {0,0,false};

  // define the original data and its initial values
  TheData OriginalData = {0,0,false};

  // define the filtered data and its initial values
  TheData FilteredData = {0,0,false};

  char UserInput;

  // loop until the user wishes to exit
  while (1) {
    
    // show the menu of options
    cout << endl;
    cout << "Filter Menu" << endl;
    cout << "-----------" << endl;
    cout << "1. Enter data for filtering" << endl;
    cout << "2. Enter filter values" << endl;
    cout << "3. Apply filter" << endl;
    cout << "4. Display filtered data" << endl;
    cout << "5. Exit from the program" << endl << endl;
    
    // get the user's choice
    cout << "Enter your option: ";
    cin >> UserInput;
    cout << endl;
    
    // act on the user's input
    switch(UserInput) {
      case '1':
        EnterData(OriginalData);
        FilteredData.Valid = false;
        break;

      case '2':
        EnterFilter(Filter);
        FilteredData.Valid = false;
        break;      
 
      case '3':
        if (Filter.Valid == true && OriginalData.Valid == true &&
            FilteredData.Valid == false) {
          if (ApplyFilter(Filter,OriginalData,FilteredData) == FILTER_TOO_LONG) {
             cout << "The filter must not be longer than the data" << endl;
          }
          else {
            FilteredData.Valid = true;
            cout << "Filter applied" << endl;
          }
        }
        break;

      case '4':
        if (Filter.Valid == true && OriginalData.Valid == true &&
            FilteredData.Valid == true) {
          DisplayData(Filter,OriginalData,FilteredData);
        }
	 else {
	    cout << "Data have not yet been filtered" << endl;
	 }
        break;

      case '5':
        delete [] Filter.Values;
        delete [] OriginalData.Values;
        delete [] FilteredData.Values;
        return 0;
        break;

      default:
        cout << "Invalid entry" << endl << endl;
        break;
    }
  }
}

// Allow the user to enter the data to be filtered
// Arguments:
//   (1) the structure containing the input data
// Returns: nothing
// 
void EnterData(TheData& GetData)
{  
  // initialize the data structure that holds the data to be filtered, including getting
  // the number of data values from the user
  delete [] GetData.Values;
  cout << "How many data values do you wish to enter: ";
  cin >> GetData.Length;
  GetData.Valid = true;

  // allocate memory to the data
  GetData.Values = new double[GetData.Length];
  if (GetData.Values == 0) {
    cout << "Unable to allocate sufficient memory" << endl;
    exit(1);
  }

  // obtain all of the data values
  cout << endl;
  cout << "Enter the data values" << endl;
  cout << "---------------------" << endl;
  for (unsigned long CountData = 0; CountData < GetData.Length; CountData++) {
    cout << "Enter value " << CountData+1 << ": ";
    cin >> GetData.Values[CountData];
  }
}

// Allow the user to enter the filter values
// Arguments:
//   (1) the structure of the filter to be defined
// Returns: nothing
// 
void EnterFilter(TheFilter& GetFilter)
{  
  // initialize the data structure that holds the filter, including getting the number of
  // filter values from the user
  delete [] GetFilter.Values;
  cout << "How many data values do you wish to enter: ";
  cin >> GetFilter.Length;
  GetFilter.Valid = true;

  // allocate memory to the filter values
  GetFilter.Values = new double[GetFilter.Length];
  if (GetFilter.Values == 0) {
    cout << "Unable to allocate sufficient memory" << endl;
    exit(1);
  }

  // obtain all of the filter values
  cout << endl;
  cout << "Enter the filter values" << endl;
  cout << "-----------------------" << endl;
  for (unsigned long CountData = 0; CountData < GetFilter.Length; CountData++) {
    cout << "Enter value " << CountData+1 << ": ";
    cin >> GetFilter.Values[CountData];
  }
}

// Apply the filter to the input data and store in the filtered data structure
// Arguments:
//   (1) the structure of the filter to be applied
//   (2) the structure containing the data to be filtered
//   (3) the structure to hold the filtered data
// Returns: OK - if the filter is applied
//          FILTER_TOO_LONG - the filter is longer than the data 
//  
int ApplyFilter(TheFilter Filter, TheData DataIn, TheData& DataOut)
{  
  // return an error if the filter is longer than the data
  if (Filter.Length > DataIn.Length) return FILTER_TOO_LONG;

  // initialize the data structure that holds the filtered data
  delete [] DataOut.Values;
  DataOut.Length = DataIn.Length - Filter.Length + 1;

  // get memory for the filtered data
  DataOut.Values = new double[DataOut.Length];
  if (DataOut.Values == 0) {
    cout << "Unable to allocate sufficient memory" << endl;
    exit(1);
  }

  // apply the filter to the data
  for (unsigned long CountData = 0; CountData < DataOut.Length; CountData++) {
    DataOut.Values[CountData] = 0.0; 
    for (unsigned long CountFilter = 0; CountFilter<Filter.Length; CountFilter++) {
      DataOut.Values[CountData] += DataIn.Values[CountData+CountFilter] *
                                   Filter.Values[CountFilter]; 
    }
  }

  return OK;
}


// Display input data, filter values and output data
// Arguments:
//   (1) the structure of the filter to be applied
//   (2) the structure containing the data to be filtered
//   (3) the structure that holds the filtered data
// Returns: nothing
// 
void DisplayData(TheFilter Filter, TheData DataIn, TheData DataOut)
{  
  // display all of the input data values
  cout << endl;
  cout << "The input data values" << endl;
  cout << "---------------------" << endl;
  cout << "[ ";
  for (unsigned long CountData = 0; CountData < DataIn.Length; CountData++) {
    cout << DataIn.Values[CountData] << " ";
  }
  cout << "]" << endl;
    
  // display all of the filter values
  cout << endl;
  cout << "The filter values" << endl;
  cout << "-----------------" << endl;
  cout << "[ ";
  for (unsigned long CountData = 0; CountData < Filter.Length; CountData++) {
    cout << Filter.Values[CountData] << " ";
  }
  cout << "]" << endl;
    
  // display all of the data output values
  cout << endl;
  cout << "The data output values" << endl;
  cout << "----------------------" << endl;
  cout << "[ ";
  for (unsigned long CountData = 0; CountData < DataOut.Length; CountData++) {
    cout << DataOut.Values[CountData] << " ";
  }
  cout << "]" << endl;
}

The difference between an procedural and an object-oriented approach is how you bind the data and the functions that do something to it together.

Right now you have a very good structure. You have structs that hold the data, and functions that operate on specific data.

What you need to do is turn those structs into classes, and make the procedures and functions that operate on the structs into member functions (or methods) of the class.

Keep in mind that the member functions should generally work only on its own data. So, for example, you have the functions EnterData() and EnterFilter(). The first would make a good method of your TheData class, and the second would make a good method in your TheFilter class.

Your DisplayData() function operates on a number of different structures, but not any of them at the same time. Each of your classes, then, should have a DisplayData() method, which only displays it's own data.

You will have to decide how to use the filter class and the data classes all together. You could:

  1. Make a method of the TheFilter class take a TheData object and return a new TheData object.
  2. Make a method of the TheData class take a TheFilter object and filter its own data.
  3. etc.

You will still have to create three separate variables, but instead of assigning them initial (or default) values in main(), have the class constructor initialize the data.


Personally, I would also recommend that you use a vector instead of a newed array, but that is not essential to the assignment and you can save it for later (or not at all).


Hope this helps.

Duoas,

Thank you for your helpful reply!

It's such a relief that you've advised me to change the TheData and TheFilter structs to classes, as I had done this already - although I had no confidence that what I was doing was correct.

The areas that were really confusing me were the ApplyFilter() and the DisplayData() functions, and how to interface these with the TheFilter and TheData classes that I had assigned.

On your advice, I'll place separate DisplayData() member functions in the TheData and TheFilter class definitions. I can see your point about the different ways to implement the ApplyFilter() function.

Will you be available on the forum to help me further with this assignment, just in case any problems arise?

Once again, thanks so much for your help and your time.

Zeke

Glad to be of help. I check in fairly regularly, but if I'm not here someone else will surely help. There are a fair number of knowledgeable people here who are always willing to help.

Have fun! :)

zekester:

Here's some additional thoughts. If you find them useful, fine, if not, so be it.

Just so you know (I'm sure Duoas already does) there is no need to change the struct declarations into class declarations. In C++ the only distinction between classes and structs is that data members are declare with private access in classes and public access in structs. Some people still retain the feel of C style structs withing their programs by using structs in C++ only if the class doesn't contain member functions, but that isn't necessary.

If you are going to declare memory dynamically for a data member of a class, it is probably a good idea to have each class allocate and delete memory used rather than trying to do it within the program. This is frequently, though not always, done in the constructor and destructor respectively, in addition to the assignment and copy operators as needed. Of course, if you don't have to do your own memory handling, then don't.

To demonstrate inheritance in your program you could declare an abstract base class called Form (or some other name) which has the same data members as TheData and TheFilter with a pure virtual member function called displayData. The inherited classes could also hold unique member functions to do tasks unique to their existence. This may be a bit strained intellectually because TheData and TheFilter classes aren't related like Dogs and Cats are both Animals, but reality is frequently distorted for educational purposes. It's up to you, of course.

An additional twist, primarily for educational purposes again, might be to have a working class called FilterProgram which contains members from both of the original classes as needed, rather than declaring them from witin main(), and has all the menus, etc within it which can be called using appropriate member functions, etc. Doing it this way it is sometimes possible to write the program like this:

#include "FilterProgram"
int main()
{
    FilterProgram program;
    program.run();   
}

This type of program is called a driver program because it is really just a way to access the necessary header files, where all the work is actually done. One advantage of this style is that individual files are readily available for reuse in other programs as desired, rather than having to rewrite the classes, structs, etc in each program as the need arises. This style also provides additional experience writing modular (OOP?) programs by compartmentalizing everything with the class declarations, even if it isn't necessarily the most straightforward program to read (you end up pulling up the linked files one by one to find information about each of the included classes, etc, since it's not all in one spot to allow you to track through the information needed by scrolling up and down the screen without flipping from file to file).

commented: very informative one, that is! +1

Hi Lerner,

Thanks so much for your post!

The task stipulates explicitly that we have to use classes and that all data members must be private. I appreciate that this complicates the program unnecessarily but the lecturer is trying to test my grasp of classes rather than structs.

We briefly covered constructors and destructors, copy constructors, overloading operators, friend functions and the 'this' pointer, so I expect that we have to use all of these tools to achieve full marks.

We haven't covered abstract classes or virtual member functions but I can see the merit in your suggestion of creating a new class which is able to access data from TheData and TheFilter, and to manipulate that data using its own set of member functions, especially for the ApplyFilter() and DisplayData() functions (if my understanding is wrong, please correct me!).

Duoas suggested that I have separate DisplayData() functions in my TheData and TheFilter classes, and that I make the ApplyFilter() function a member function of either class. Which approach would be better?

Your "additional twist" is incredibly helpful as the task stipulates that the main function can create the first object only. I presume that this approach could be used to compartmentalise the code into separate functions as well?

Thanks once again. I'd be lost without the help I've received on this forum.

Zeke

The base class, what I called Form, doesn't have to be abstract if that's not something you've discussed. It could be a routine base class. The advantage of being abstract is that it couldn't be instantiated since that doesn't make any sense (but straining reality is common in examples used for teaching purposes, so that's not unexpected). Again, structs can have private members as well as public (or protected for that matter). Here's a skeletal version of the inheritance heirachy using structs to demonstrate my point.

struct Form
{
   private:
      double age;
      string name;
 };

struct TheData : public Form
{
   void displayData();
};

struct TheFilter : public Form
{
    void displayData();
};

Now both TheData and TheFilter objects will contain a double and a string which have private access and their own displayData() functions which have public access, just like you can have in a class. struct declarations are just like classes except in the default access of the members and the terms are often used interchangeably. If your instructor uses the term class in the narrow sense, then by all means declare each user defined type as a class. But you should know that in C++ there's nothing you can do with a class you can't do with a struct if you want. (NB, the code snippet above is far from an adequate type declaration. It was posted only as an example of how you'd use keywords in declaring a struct to get it to behave exactly as a class would.)

I think Duos' suggestions were right on target given as far as he went without knowing the full restrictions of the assignment, and I just made a lucky guess. If you have a class called FilterProgram, as I suggested as a possibility, then ApplyFilter() would seemingly fit best as member function of the FilterProgram class and not in either the TheData or TheFilter classes.

When you're done with the program all of the stand alone functions in the original post would end up as member functions of one class or another.

I'm not sure I understand one part of your assignment. You stated that you are only allowed to directly instantiate one object. This makes little sense to me, as the program requires two objects to combine to produce a third. FilteredData = OriginalData + Filter (where '+' means 'apply the filter', of course).

This train of thought is validated by the fact that both the OrignalData and the Filter require user input.

While Lerner's suggestion about shared inheritance is good, given the actual structure of the classes, this is actually just a happy convenience. In reality, the filter and the data are only related because the filter modifies the data in some way. That is to say, the filter and the data are separate things, both conceptually and implementationally. (That one operates upon the other is not causal, just as their similarity in structure is not causal.)

In summation: how exactly does your professor expect you to create only one object at the start?

Thought 1:
Make FilterProgram a function, which takes as argument a TheData object (the one you create in main()), creates a TheFilter object, applies the filter, and returns to main() a filtered TheData object.

This of course will spread user I/O out between main() and FilterProgram()... (Indirectly, of course, as actual I/O should occur as you have it: in your member functions).

Thought 2:
(Is a really bad idea, so I won't share it.)


:-/ hmm...

(BTW. If my thinking has confused you on any specific point, just ask.)

I totally agree with Duoas that the inheritance scenario I described is extremely contrived and the ONLY reason I would consider doing it would be to prove I could do inheritance and polymorphism in the program as presented.

I disagree with Duoas as to the flow of the program. With appropriate fleshing out of the repsective user defined types I think something like this would work fine, directly declaring just a single object in main():

struct TheData
{
   void EnterData();
   void display();
};

struct  TheFilter
{
    void EnterFilter();
    void display();
};

struct FilterProgram
{
   TheData data;
   TheFilter filter;
   TheData filteredData;
  
   void run();
   void applyFilter();
   void displayFilteredData();  
};

int main()
{
    FilterProgram program;
    program.run();
}

I think you missed my point.

First off, I expressed no disapproval of deriving TheData and TheFilter from a common ancestor. What I said was that they shouldn't be intimately familiar.

Secondly, I don't think you should be introducing new structures into the assignment. Particularly as the FilterProgram is, as you have suggested it, simply a stand-in for main(). The assignment could be just as easily, and more simply, written without it:

struct TheData
{...};

struct  TheFilter
{...};

int main()
{
    TheData data;
    TheFilter filter;
    TheData filteredData;
    ...
}

Your solution sidesteps the problem of instantiating a single object in a way the teacher will not accept! Remember, keep it simple, and don't shuffle creation of things off into another function just to avoid doing it in main(). Avoiding main() is not a good enough reason to do that.

My question remains valid: how are you to initialize both the data and filter objects before filtering? And how are you to display the filtered data when done?

If your program is to behave like the original:

  1. enter data
  2. enter filter
  3. apply filter to original data --> filtered data
  4. display original, filter, and filtered data.
  5. quit.

then there must be some consideration given to the following:

  1. how are the filter and the filtered data to be displayed alongside the original data?
  2. how are the second piece of primary data (which I presume would be the filter) and the secondary data (the filtered data) to be generated and stored?

I have several related ideas as to how I would do it, very simply, but I expect zekesteer to think about it and present his own thoughts before I suggest anything further. (One possibility is that the strict structure of the original menu is not part of the assignment, just the way that zekesteer implemented it the first time through?)

Duos:

I think you are being too restrictive in your interpretation of the problem set.

>>I don't think you should be introducing new structures into the assignment.

In reading the problem set as posted I don't see where the number or name of classes to be used for the project is specified. In post #1 the project was described as:

"to convert a standard C++ program into an object-oriented one, using the principals of polymorphism, encapsulation and inheritance."

I see no restriction to creating new user defined types in that project description.

In post #6 the following restritction to the project did become available:

"the task stipulates that the main function can create the first object only."

My approach does fulfill both of the posted criteria, though I don't think they are necessarily the way I would write the program, as I've said before.

>>Particularly as the FilterProgram is, as you have suggested it, simply a stand-in for main().

I disagree. FilterProgram::run() is the substitute for main(), not the FilterPrgam header file as such. Leaving run() out of FilterProgram and leaving all the menu's etc in main() would be just fine by me, though forcing everything into a user defined type is educational, and as long as distorting reality for purposes of education is part of the process, then so be it.

>> don't shuffle creation of things off into another function just to avoid doing it in main().

Agreed. However, the reason to do it us to allow me to reuse FilterProgram as a header file in another program so I don't have to rewrite:

TheData data;
TheFilter filter;
TheData filteredData;

I'll only need to write

FilterProgram program;

and instead of including two header files I'll only need to include one. Minor conveniences for simple programs, but techniques that seem to be used for many commercial header files. In this case I don't think FilterPrgram would be a header file I'd reuse again becuase it's too contrived, but it is the tecnique underlying the creation of FilterProgram that's important here, not FilterProgram itself.

>> how are the filter and the filtered data to be displayed alongside the original data?

In post #9 I clearly give TheData and TheFilter the capacity to display themselves.

>>how are the second piece of primary data (which I presume would be the filter) and the secondary data (the filtered data) to be generated and stored?

The data storage in the user defined types would be the same as in post #1. As I indicated in post #9 the user types posted need to be significantly fleshed out.

The filtered data would be generated by FilterProgram::applyFilter(). Again the declaration and definition need to be fleshed out. The posted code is exemplary of an idea only, not workable source code.

Trying to force the original program, which I agree looks fine to me as it stands, into an OOP style is a contrived situation and I suspect (hope?) is purely for educational purposes only.

Lerner, you are assuming too much and hijacking this thread from the OP's question in an attempt to battle me. You have presented a solution. I have presented a similar train of thought. Now the OP needs to look at his homework assignment and make a decision as to how he will proceed, based on his knowledge of the problem set. Since neither you nor I know anything about the problem set other than what the OP has told us (which isn't much!), we are hardly in a position to give authoritative advice on how to interpret it.

Just because the OP, or his professor, did not prohibit x, y, and z, does not mean that we here should encourage the use of x, y, or z in his program. That is the professor's perogative. Not ours. It is too often the case that, in an effort to help, people lead beginners into things that are not part of the assignment, which is always an intentionally limited subset of programming, and thereby distract the beginner from the thing the professor expects him to be working on and learning about.

You are misunderstanding me, as you have taken pains to disagree with me about things for which we had both suggested, and you are misrepresenting my concerns. Please stop. If you really feel slighted, take it to PM and stop toasting here. I'll warn you though, I have a very comprehensive knowledge about program transformation.

Lastly, from what we do know of the assignment, it is "to force the original program ... into an OOP style"; specifically "to convert a standard C++ program into an object-oriented one". Deprecative vocabulary doesn't change things. I have already noted that his menu structure may have been of his own doing and not part of the original assignment. But at this point that is purely conjecture.

Thank you both for your help. To clear things up, I've attached the stipulations of the task below:

Before writing your code you will need to think carefully about the following (HINT: these are hints!).

1. Decide on the classes you need. You should also be careful to include a set of classes that
implement the functionality of the code in such a way that they could be used by other
developers.

2. To establish suitable member functions, take into account the responsibilities of each class; for
example, which class should perform the filtering operation?

3. Encapsulation dictates that, as far as possible, an object should be responsible for its own data.
4. Use polymorphism only as appropriate. The general rule is that if an inbuilt operator matches
the purpose of a member function then it should be overloaded.

5. Use inheritance only where necessary. Only if you can reasonable say that object A is a type of
object B should A inherit from B. For example, a car is a type of vehicle, but a table isn’t a type
of elephant (even if both have four legs).

The following also forms part of the specification (for both the part B and part C modules).

1. The program must use an object-oriented approach; it must contain only member functions
(other than main() which should do no more than create the first object).

2. All members must be private.

3. Document properly the code you add. Don’t document every line of code - just the major
blocks of code (see the marking scheme).

4. The structure of your code should be neat and easy to follow.

Hi Duoas,

I'm not sure I understand one part of your assignment. You stated that you are only allowed to directly instantiate one object.

My apologies for not being more forthcoming with the stipulations of the task. From my understanding, main() can only instantiate one object; this object can then go on to instantiate the remaining objects.

My progress so far. The two areas that I'm having difficulty with are those that Lerner and Duoas have been (heatedly!) discussing; namely, outsourcing the code in main() and implementing the ApplyFilter() function using the best possible approach.

Please note that:

1. For simplicity, I've instantiated all objects within main() for the time being. The task requires that main() instantiate the first object only, so this must be changed.

2. I toyed with the idea of having the ApplyFilter() function read and write to the private members of the TheData class using the SetValue() and ShowValue() functions respectively. This seemed a pretty clumsy approach so I aborted half-way through. The functions remain but can be ignored.

Please continue to share your comments and suggestions!

Thanks,

Zeke

#include <iostream>
using namespace std;

// ------CLASS DEFINITIONS------

class TheData 
{
public:								
	TheData(double* = 0, unsigned long = 0, bool = false);			
	~TheData();						

	void SetValue(double*, TheData&);
	void SetValue(unsigned long, TheData&);
	void SetValue(bool, TheData&);

	double* ShowValue_Values();
	unsigned long ShowValue_Length(); 
	bool ShowValue_Valid();

	void EnterData(TheData& OriginalData);				
	void DisplayData(TheData OriginalData, TheData FilteredData) const;	
private:							
	double* Values;						
	unsigned long Length;						
	bool Valid;							
};

class TheFilter
{
public:
	TheFilter(double* = 0, unsigned long = 0, bool = false);		
	~TheFilter();						
	
	void EnterFilter(TheFilter& Filter);					

	void SetValue(double*, TheFilter&);
	void SetValue(unsigned long, TheFilter&);
	void SetValue(bool, TheFilter&);

	double* ShowValue_Values();
	unsigned long ShowValue_Length(); 
	bool ShowValue_Valid();

	int ApplyFilter(TheData OriginalData, TheFilter Filter, TheData& FilteredData);
	void DisplayData(TheFilter Filter) const;				
private:								
	double* Values;						
	unsigned long Length;						
	bool Valid;							
};

/*class PrivateData : public TheData, public TheFilter
{
public:
	double* Values;																
	unsigned long Length;														
	bool Valid;	
};*/

enum {OK,FILTER_TOO_LONG};		// Function return values

// ------------MAIN------------

int main()
{
	// main() to create the first object only!
	TheData OriginalData;						TheData FilteredData;							TheFilter Filter;											
	OriginalData.EnterData;						OriginalData.DisplayData;											
	char UserInput;

	// loop until the user wishes to exit
	while (1) {
	    
		// show the menu of options
		cout << endl;
		cout << "Filter Menu" << endl;
		cout << "-----------" << endl;
		cout << "1. Enter data for filtering" << endl;
		cout << "2. Enter filter values" << endl;
		cout << "3. Apply filter" << endl;
		cout << "4. Display filtered data" << endl;
		cout << "5. Exit from the program" << endl << endl;
	    
		// get the user's choice
		cout << "Enter your option: ";
		cin >> UserInput;
		cout << endl;
	    
		// act on the user's input
		switch(UserInput) {
		case '1':
			OriginalData.EnterData(OriginalData);
			FilteredData.SetValue(false, OriginalData);		
			break;

		case '2':
			Filter.EnterFilter(Filter);
			FilteredData.SetValue(false, FilteredData);
			break;      
	 
		case '3':
			if (Filter.ShowValue_Valid() == true && OriginalData.ShowValue_Valid() == true && FilteredData.ShowValue_Valid() == false) 
			{
				if (Filter.ApplyFilter(OriginalData, Filter, FilteredData) == FILTER_TOO_LONG) 
				{
					cout << "The filter must not be longer than the data." << endl;
				}
				else 
				{
					FilteredData.SetValue(true, FilteredData);
					cout << "Filter applied." << endl;
				}
			}
			break;

		case '4':
			if (Filter.ShowValue_Valid() == true && OriginalData.ShowValue_Valid() == true && FilteredData.ShowValue_Valid() == true) 
			{
				OriginalData.DisplayData(OriginalData, FilteredData);
				Filter.DisplayData(Filter);
			}
			else 
			{
				cout << "Data have not yet been filtered" << endl;
			}
			break;

		case '5':
		/*delete [] Filter.Values;
			delete [] OriginalData.Values; UNSURE WHAT TO DO HERE!
			delete [] FilteredData.Values;*/					return 0;
			break;

		default:
			cout << "Invalid entry" << endl << endl;
			break;
		}
	}
}

// --THEDATA MEMBER FUNCTIONS--

TheData::TheData(double*, unsigned long, bool)
{
	Values = 0;
	Length = 0;		
	Valid = false;						
}

TheData::~TheData()																// TheData destructor function
{
	/*delete[] OriginalData.Values;					
	delete[] OriginalData.Length;
	delete[] OriginalData.Valid;
																					// Free memory
	delete[] FilteredData.Values;
	delete[] FilteredData.Length;
	delete[] FilteredData.Valid;*/
}

void TheData::EnterData(TheData& OriginalData)
{
	// initialize the data structure that holds the data to be filtered, including getting
	// the number of data values from the user
	delete[] OriginalData.Values;
	cout << "How many data values do you wish to enter: ";
	cin >> OriginalData.Length;
	OriginalData.Valid = true;

	// allocate memory to the data
	OriginalData.Values = new double[OriginalData.Length];
	
	if (OriginalData.Values == 0) 
	{
		cout << "Unable to allocate sufficient memory." << endl;
		exit(1);
	}

	// obtain all of the data values
	cout << endl;
	cout << "Enter the data values" << endl;
	cout << "---------------------" << endl;
	
	for (unsigned long CountData = 0; CountData < OriginalData.Length; CountData++) 
	{
		cout << "Enter value " << CountData + 1 << ": ";
		cin >> OriginalData.Values[CountData];
	}
}

void TheData::DisplayData(TheData OriginalData, TheData FilteredData) const
{
	// display all of the input data values
	cout << endl;
	cout << "The input data values" << endl;
	cout << "---------------------" << endl;
	cout << "[ ";
	
	for (unsigned long CountData = 0; CountData < *OriginalData.Values; CountData++) 
	{
		//cout << OriginalData.SetValue(OriginalData.Values[CountData], OriginalData) << " ";
	}
	
	cout << "]" << endl;
	
	// display all of the data output values
	cout << endl;
	cout << "The data output values" << endl;
	cout << "----------------------" << endl;
	cout << "[ ";

	for (unsigned long CountData = 0; CountData < FilteredData.Length; CountData++) 
	{
		//cout << FilteredData.SetValue(FilteredData.Values[CountData], FilteredData) << " ";
	}
	cout << "]" << endl;
}

void TheData::SetValue(double* SetValue, TheData& Original_or_Filtered_Data)
{
	Original_or_Filtered_Data.Values = SetValue;
}

void TheData::SetValue(unsigned long SetValue, TheData& Original_or_Filtered_Data)
{
	Original_or_Filtered_Data.Length = SetValue;
}

void TheData::SetValue(bool SetValue, TheData& Original_or_Filtered_Data)
{
	Original_or_Filtered_Data.Valid = SetValue;
}

double* TheData::ShowValue_Values()
{
return 0;
}

unsigned long TheData::ShowValue_Length()
{
return 0;
}

bool TheData::ShowValue_Valid()
{
return false;
}

// -THEFILTER MEMBER FUNCTIONS-

TheFilter::TheFilter(double*, unsigned long, bool)			
{
	Values = 0;		
	Length = 0;				
	Valid = false;	
}

TheFilter::~TheFilter()		
{
	// delete[] "something"?														// Free memory
}

void TheFilter::EnterFilter(TheFilter& Filter)
{
	// initialize the data structure that holds the filter, including getting the number of
	// filter values from the user
	delete [] Filter.Values;
	cout << "How many data values do you wish to enter: ";
	cin >> Filter.Length;
	Filter.Valid = true;

	// allocate memory to the filter values
	Filter.Values = new double[Filter.Length];

	if (Filter.Values == 0) 
	{
		cout << "Unable to allocate sufficient memory" << endl;
		exit(1);
	}

	// obtain all of the filter values
	cout << endl;
	cout << "Enter the filter values" << endl;
	cout << "-----------------------" << endl;

	for (unsigned long CountData = 0; CountData < Filter.Length; CountData++) 
	{
		cout << "Enter value " << CountData + 1 << ": ";
		cin >> Filter.Values[CountData];
	}
}

int TheFilter::ApplyFilter(TheData OriginalData, TheFilter Filter, TheData& FilteredData)
{
	// return an error if the filter is longer than the data
	if (Filter.Length > OriginalData.Length) return FILTER_TOO_LONG;
	
	// initialize the data structure that holds the filtered data
	delete[] FilteredData.Values;
	FilteredData.Length = OriginalData.Length - Filter.Length + 1;
	
	// get memory for the filtered data
	FilteredData.Values = new double[FilteredData.Length];
	
	if (FilteredData.Values == 0) 
	{
		cout << "Unable to allocate sufficient memory" << endl;
		exit(1);
	}

	// apply the filter to the data
	for (unsigned long CountData = 0; CountData < FilteredData.Length; CountData++) 
	{
		FilteredData.Values[CountData] = 0.0;
		
		for (unsigned long CountFilter = 0; CountFilter < Filter.Length; CountFilter++) 
		{
			FilteredData.Values[CountData] += OriginalData.Values[CountData + CountFilter] * Filter.Values[CountFilter];
		}
	}
	return OK;
} 

void TheFilter::DisplayData(TheFilter Filter) const
{
	// display all of the filter values
	cout << endl;
	cout << "The filter values" << endl;
	cout << "-----------------" << endl;
	cout << "[ ";
	
	for (unsigned long CountData = 0; CountData < Filter.Length; CountData++) 
	{
		//cout << Filter.SetValue(Filter.Values[CountData], Filter) << " ";
	}
	
	cout << "]" << endl;
}

void TheFilter::SetValue(double* SetValue, TheFilter& Filter)
{
	Filter.Values = SetValue;
	//cout << "Testing Values: " << Filter.Values << " = " << SetValue;	
}

void TheFilter::SetValue(unsigned long SetValue, TheFilter& Filter)
{
	Filter.Length = SetValue;
	//cout << "Testing Length: " << Filter.Length << " = " << SetValue;	
}

void TheFilter::SetValue(bool SetValue, TheFilter& Filter)
{
	Filter.Valid = SetValue;
	//cout << "Testing Valid: " << Filter.Valid << " = " << SetValue;	
}

double* TheFilter::ShowValue_Values()
{
	double* ShowValue;
	ShowValue = 0; //Filter.Values;	

	return ShowValue;
}

unsigned long TheFilter::ShowValue_Length()
{
	unsigned long ShowValue;
	ShowValue = 0; //Filter.Length;

	return ShowValue;
}

bool TheFilter::ShowValue_Valid()
{
	bool ShowValue;
	ShowValue = false; //Filter.Valid;

	return ShowValue;
}

P.S. Sorry that the code is pretty hard to read! I'll provide the .cpp file if required.

I haven't looked through your last posting too closely yet...

2 and related stuff
It appears that your teacher expects ApplyFilter() to be a member function of either TheData or TheFilter. You must choose one, and be able to justify your choice to the professor.

The ApplyFilter() function, however should never access private members of any foreign class. In other words, if it is TheData::ApplyFilter() then it should not access the private members of TheFilter. Likewise, if it is TheFilter::ApplyFilter() then it should not access the private members of TheData. Hopefully, thinking about this will help you choose which class should have the ApplyFilter() method.

The foreign class should only be accessed by accessor methods. In other words, to access any data element of TheData, write member functions double TheData::getValue( int index ) and void TheData::setValue( int index, double value ) This allows controlled access to TheData's data, and as part of the public interface, is OK to use anywhere. The same holds true for TheFilter.

inheritance
It appears from hint #5 that your teacher will not permit you to derive TheFilter and TheData from the same ancestor class. Just because they look similar is not sufficient to say that they are the same kind of thing.

Unless you can reasonably argue that they are, in fact, variations of the same thing.

1 and thoughts
Lerner suggests that you overcome the obstacle by creating a third class, which encapsulates the program and contains a member function ("run()") which creates two TheData and one TheFilter. This might be considered a way of satisfying hint #1. (I don't like it though, but that is my personal opinion.)

I still don't know if you must make your program interact with the user exactly like the original or not, but if not, you might work it this way:

  1. create the OriginalData object in main()
  2. the OriginalData object creates a Filter object and uses it to filter itself

There are things I dislike about this as well.

Remember, the professor wants you to make things friendly to "other developers" as well, so you will want to expose both the TheData and TheFilter classes. However, what this particular program does with them is, obviously, specific to this particular program, and does not need to be made particularly exportable for other users.

Thus, TheFilter and TheData should not have stuff in them that are specific to this program.

One solution might be something of a combination of our two ideas: which is to create another class which main() directly manipulates to create and filter data.

Hope this gives you some ideas.

Oh yeah, I don't see any reason why you would have to overload any operators.

I do advocate using a third class, as I don't like the idea of the data modifying itself or the filter forcing itself on the data. Given the information in post #13 I would argue against deriving TheData and TheFilter from a common class and I wouldn't use a run() in the third class--both are way too contrived to fit the guidelines.

The following outline would cause the least distortion of reality in my view:

class FilteredData 
 private
   TheData data
   TheFilter filter
   double * filteredData
 public
   populate()
   applyFilter()
   display()

main()
  FilteredData fd
  fd.populate()
  fd.applyFilter()
  fd. display()

Though, given the relationship of a filtered set of objects and an unfiltered set of objects this might work as well:

class TheFilteredData : public TheData
  private
    double * filteredData
  public
    applyFilter(TheFilter)

I would also consider implementing the objective of these:

double TheData::getValue( int index )

void TheData::setValue( int index, double value )

by overloading the [] operator given this statement in post #13

"The general rule is that if an inbuilt operator matches the purpose of a member function then it should be overloaded."

but then again that's just my thought.

Thank you both for your help and advice. I now have the program working correctly although it still needs a great deal of refinement.

I favour Lerner's approach of a third class for the filtered data as it appears tidier and - if I inherit the member functions and private members from TheData as he suggested - it allows me to use inheritance. However, I recognise that I'm not necessarily trying to build the best program, but rather, the program which best meets the requirements set by my lecturer.

For the time being, I've made ApplyFilter() a member function of the TheData class (as Duoas suggested), accessing TheFilter's private members through the ShowValue and SetValue member functions. As you both know, my experience with objects is limited and I'm unsure if this is the best approach (Duoas says yes, Lerner says no). It'd be wonderful if you could both agree on whether or not the creation of a separate FilteredData class would best meet the task criteria set by my lecturer! I'd much rather obtain the highest possible marks than build the best possible program. :)

I'm also a little unsure about the constructor functions. EnterData() and EnterFilter() each allocate memory (using "new") for the "values" members in their respective classes. Would a better approach not be to have the memory allocated within the TheData and TheFilter constructor functions instead? The only problem with this is that the number of data and filter values aren't known until EnterData() and EnterFilter() are actually called, when the user is asked "How many data values do you wish to enter: "... Can either of you see anyway around this obstacle?

P.S. Lerner, please could you explain what you meant when you suggested overloading the [] operator?

Lerner made a brilliant observation in noticing that the [] operator could be used to access data elements. See here for an example. And here. And here. You might want to change TheFilter's ShowValue and SetValue methods to use this subscript operator... (if you want).

I disagree that TheData and TheFilteredData should be separate classes (and I didn't get the impression that Lerner advocated it either...) simply because both hold the same kind of data: a list of values. Just because one has been transformed differently than the other doesn't change the specific characteristics of the data itself. No qualified professor will give you better marks for a poorly written program just to fit some perceived criterion. The best possible program best satisfies the program requirements. Make sense?

The constructor only needs to create and initialize the object so that it is usable in the sense that it can be handled without generating serious errors. That doesn't mean that it has to have all its data defined/allocated/whatever, or that it has to be of any actual use. You've thought about it correctly. Where, then, do you think is the best place to use new?

Hope this helps.

Hi Duoas and Lerner,

The program is almost complete and I’m very happy with it! Just the following issues remain:

1. The member function, ApplyFilter() currently reads:

int TheData::ApplyFilter([B]TheData OriginalData, TheFilter Filter, TheData& FilteredData[/B])
{
	// return an error if the filter is longer than the data
	if (Filter.Length() > [B]OriginalData[/B].length) return FILTER_TOO_LONG;
	
	// initialize the data structure that holds the filtered data
	delete[] [B]FilteredData[/B].values;
	[B]FilteredData[/B].length = [B]OriginalData[/B].length - Filter.Length() + 1;
	
	// get memory for the filtered data
	[B]FilteredData[/B].values = new double[[B]FilteredData[/B].length];
	
	if ([B]FilteredData[/B].values == 0) 
	{
		cout << "Unable to allocate sufficient memory" << endl;
		exit(1);
	}

	// apply the filter to the data
	for (unsigned long CountData = 0; CountData < [B]FilteredData[/B].length; CountData++) 
	{
		[B]FilteredData[/B].values[CountData] = 0.0;
		
		for (unsigned long CountFilter = 0; CountFilter < Filter.Length(); CountFilter++) 
		{
			[B]FilteredData[/B].values[CountData] += [B]OriginalData[/B].values[CountData + CountFilter] * Filter.operator [](CountFilter);
		}
	}
	return OK;
}

I'd like ApplyFilter() to read something like this:

int TheData::ApplyFilter()
{
	// return an error if the filter is longer than the data
	if (Filter.Length() > length) return FILTER_TOO_LONG;
	
	// initialize the data structure that holds the filtered data
	delete[] values;
	length = length - Filter.Length() + 1;
	
	// get memory for the filtered data
	values = new double[length];
	
	if (values == 0) 
	{
		cout << "Unable to allocate sufficient memory" << endl;
		exit(1);
	}

	// apply the filter to the data
	for (unsigned long CountData = 0; CountData < length; CountData++) 
	{
		values[CountData] = 0.0;
		
		for (unsigned long CountFilter = 0; CountFilter < Filter.Length(); CountFilter++) 
		{
			values[CountData] += values[CountData + CountFilter] * Filter.operator [](CountFilter);
	}
	return OK;
}

By removing the references to the OriginalData and FilteredData objects however, there is no way to differentiate between the members of OriginalData and FilteredData (values, length and valid). Does anyone know if what I’m trying to do is possible, or will I have to maintain the existing format?

2. Using the suggestion from Lerner and the links that Duoas provided, I've overloaded the [] operator to allow TheData::ApplyFilter() to access TheFilter::values. The code is shown below.

class TheFilter {
public:
double& operator[] (unsigned int Index) {return values[Index];} 
const double& operator[] (unsigned int Index) const {return values[Index];}
...};

To access TheFilter::values from TheData::ApplyFilter(), I’m using the following statement: Filter.operator [](CountFilter). Is this correct? When I use the statement: Filter.values[CountFilter] I receive an error at compilation: “cannot access private member declared in class ‘TheFilter’.

3. I’m receiving a runtime error ”_BLOCK_TYPE_IS_VALID” when ApplyFilter() and DisplayData() are called, and when the program exits. I read online that this error is caused when the destructor functions are called twice in succession, so I commented out their bodies and the program runs fine. For example, for the TheFilter destructor:

class TheFilter
{
public:									~TheFilter();
	…
}

…
	
TheFilter::~TheFilter()																
{
	// delete[] values;
}

However, I actually require the destructor functions. I need a way to address this runtime error whilst maintaining the functionality of the destructor functions.

4.

Where, then, do you think is the best place to use new?

I'd hazard a guess that the best place would be in the EnterFilter() and EnterData() member functions… am I correct?!

Zeke

1. The prototype should read like one of the following int TheData::ApplyFilter( TheFilter &Filter ) (operates on self) TheData TheData::ApplyFilter( TheFilter &Filter ) (returns new data)

Personally, I would have made ApplyFilter a method of the TheFilter class... but so long as you can justify your use you are fine.

2. The point of overloading operators is to make hard things easy. So, you were using it correctly when you got the error message. The error message is because there is no member field named "values". There is, however, one named "Values". (Don't slap yourself too hard.)

The procedures themselves should verify that the index is valid:

double& operator[] (unsigned int Index) {
  if ((Index < 0) || (Index >= Length))
    throw 0; // throw some appropriate exception here, instead of int.
  return Values[Index];
  }

My sample code just throws an int, but you should include <exception> and throw something like std::out_of_range.

3. That is because you are having problems with the default copy constructor, which only makes a shallow copy. Thus, when a temporary copy of your object is deleted, it deletes the original object's data. Then, when you delete the original object, the heap complains.

Make sure to have both a copy constructor and an overloaded assignment operator that makes a deep copy of the data. (This will be useful in your assignment.)

Use Google to learn about shallow and deep copies.

4. Don't hazard. Just do it. ;)

Hi Duoas,

Thanks for your reply!

1. I think I can see your reasoning; if I make ApplyFilter() a method of the TheFilter class then it need only accept data of class, TheData (i.e. int ApplyFilter(TheData, TheData). Thus, I can reference the objects, OriginalData and FilteredData, individually. Problem solved!

2. Oops... I forgot to mention that I'd made the members lowercase and member functions titlecase to allow me to use, for example, .length and .Length(). As far as I can tell, unable to use the syntax object.values[], which seems strange as the operator [] is overloaded in the public areas of both the TheFilter and TheData classes. Hmm...

3. Thank you for clearing this up for me! I'll resolve this later tonight.

4. I was right! Seems all this talk about OOP is really rubbing off on me... :D

Hi Duoas and Lerner,

Just a quick post to thank you for your help. I received my mark yesterday; 27/30!

Many thanks,

Zeke

Zeke,

By any chance do you have the final version of your coursework?
If so could you send me a link to it, or post it on here.

Thanks
Step-hen

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.