Class constructor assistance

Hi, so I have an assignment for a CS course that basically requires us to use a class to manage certain activities. We are required to have a dynamically allocated array of activities with the pointer being in the class' private section. I get how to receive info but I'm having issues with the class constructor/destructor as I do not quite know how they work?

Below is my ".h" file:

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
  #include <iostream>
  #include <cctype>
  #include <cstring>
  #include <fstream>
  using namespace std;

  const int SIZE = 40;

  struct activityInfo
  {
    char activity_name[SIZE];
    char description[SIZE];
    char location[SIZE];
    int distance;
    char notes[SIZE];
  }

  class activities
  {
    public:
     activities();
     activities(int max);
     ~activities();
     void new_activity();
     void display_area(int miles);
     void display_all();

    private:
     activityInfo * activity_pointer;
     int max;
  }

And this is my ".cpp":

  activities::activities()
  {
    activity_pointer = NULL; //need help with this part
  }

  activities::activities(int max)
  {
    max = 0; //also not sure if this is working
  }

  activities::~activities()
  {
    delete [] activity_pointer;
  }

  void activities::new_activity()
  {
    char response = 'Y';
    int i = 0;

    cout << "Please enter max number of activities: ";
    cin >> max;

    activity_pointer = new activityInfo[max];

    while(i < max && toupper(response) == 'Y')
    {
      cout << "Please enter in the name of the activity: ";
      cin >> activity_pointer -> activity_name;
      cin.ignore(100,'\n');
      . 
      . //continue with storing the rest of the activity's info
      .
      activity_pointer++;
      i++;
      
      cout << "Would you like to enter in another activity (Y/N)? ";
      cin >> response;
      cin.ignore(100,'\n');
    }
Your constructor is called when the object is created. You don't call it yourself - it just happens. You'll have to pass parameters to it when you create it if you don't have a default constructor (one that takes 0 arguments).
Your destructor is called when the object gets deleted/goes out of scope. You don't have to call it yourself (although you can manually delete your object if you really want to)

However, you can do whatever you want inside those two functions if you define your own. They will still be called automatically even if you define your own.

If you need an objects member variable to be dynamically allocated, you do that in the constructor. You never know when it'll be needed - it could be needed immediately.
If you dynamically allocated something inside your class, you can delete it inside the destructor. Since the destructor is automatically called for you, the deletion of that dynamically allocated item will be done right when the object is no longer needed. You cannot delete it before then, as you don't know if the object no longer needs it.

Your destructor is already doing the deletion. Do the opposite of that in the constructor.
right so I changed the code to:

activities::activities()
{
activity_pointer = new activityInfo[max]
}

compiled, ran and gave me this error:

*** Error in './a.out'. free(). invalid pointer.
Aborted.

I know it has to do with deleting pointer memory that was never there but I don't understand why allocation of memory isn't happening when i have new?
There isn't enough context to know for certain what is causing your problem, but it seems likely that you are copying or assigning an activities object somewhere since you don't properly handle either case.
Last edited on
Well, in your default constructor, you don't know how many you need.

1
2
3
4
5
6
7
8
9
10
11
12
  activities::activities()
  {
    activity_pointer = NULL; //need help with this part
    max = 0; //You don't know how many you need, right?  Give it some default value
  }

  activities::activities(int maxNumber)
  {
    //max = 0; //also not sure if this is working
    //We DO know how many we need, they told us.  It's maxNumber of them
    max = maxNumber; // Don't use the same name for an argument as a member variable
  }


If you're sending a parameter to the constructor, you do know how many you'll need. You need maxNumber of them, so assign "max" to that. In your default constructor, you don't know how many, so you should assign it some default value.

In your destructor, you can check to make sure your pointer isn't NULL before attempting to delete it.


I can't test your code, or any changes, because you didn't put complete code.
sorry I'm just so confused right now..

as far as I know, both
1
2
3
4
5
activities::activities() 

&

activities::activities(int max)


are constructors for this assignment. I follow you on the first constructor as we needed a dynamically allocated array for activities which activity_pointer is pointing to and an int for how many activities which I assume is what max = 0; is supposed to do since I don't know yet how many activities the user wants.

I'm supposed to somehow ask the user how many activities will be the maximum number of activities allowed but I'm not sure if I ask for that in the second constructor or in my new_activity function?
You can actually do both. You can use the constructor with the argument to allow the user to tell you how many right when the object is created. If they decide to use your default constructor, you'll have to prompt them before allocating your array.

If you want to be extra-careful, you can check if your pointer isn't NULL when you ask them how many activities there are. If it isn't NULL, then they've already told you how many. You can either just use the array you already have or delete it and allocate a new one.

If you they use the default constructor, assign the pointer to NULL so that you can check later. It's good practice to initialize everything, even pointers.
Whoops. Forgot to include:
You'll need to do the "new" inside the constructor that takes "maxNumber" as an argument. I didn't put that in there. Without it, it won't actually be allocated.
Okay so this is an update on my 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
activities::activities()
{
  activity_pointer = NULL;
  activity_count = 0;  //this is for counting how many activities are stored
}

activities::activities(int max)
{
  activity_pointer = new activityInfo [max];
}

activities::~activities()
{
  delete [] activity_pointer;
}

void activities::new_activity()
{
  char response = 'Y';
  int i = 0;

  cout << "Please enter max number of activities: ";
  cin >> max;  //so is this line actually storing user's input into 'max'? because otherwise my second constructor wouldn't work...and that might be why I got a seg fault.

  while(i < max && toupper(response) == 'Y')
  {
    cout << "Please enter in the name of the activity: ";
    cin >> activity_pointer -> activity_name;   //I actually got the seg fault right after this line executes
    .
    . //continue receiving input for description/location/etc.
    .
    activity_pointer++;
    i++;

    cout << "Would you like to enter in another activity (Y/N)? ";
    cin >> response;
    cin.ignore(100,'\n');
   }
}
Why do you want to increment your pointer? I would just index through your array.

activity_pointer[i]. Just make sure that "i" doesn't go out of bounds.

And activity_points is an array. Provide an index when reading something in.

This is in C++, so there's no reason to use raw character arrays for storing input. If the user inputs more than the maximum number of characters, then the character array will no longer have a terminating character at the end. Calls to functions that do not do bounds checking will start corrupting memory. Ever heard of a buffer overflow bug? This is a huge problem in C, but we are using C++, and we have the luxury of using very well-designed classes to help prevent these issues.
I'd just use std::string to store user input.
is activity_pointer an array? its a pointer that allocates memory for an array of structs but I'm not sure if I can use activity_pointer[i] for indexing...
Again, I'm still very new to this pointer stuff so not 100% sure but I just tried activity_pointer[i] and it gave me a huge error message.

And as far as I know, our professor doesn't allow us usage of the 'string' type, only arrays of characters. don't really know why.

And btw, thanks a ton for all your help thus far. I've been on the verge of pulling my hair out over this constructor stuff these past few days lol
Incoming wall of text explanation:

Pointers are weird. I hated them when I first started learning them. But they allow us to do so many more things that would be next to impossible without them.

Try this out for size:
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
#include <string>
#include <iostream>

struct myStruct
{
	std::string structString;
};


int main()
{
	myStruct *structPointer;
	structPointer = new myStruct[5];


	for (auto i = 0; i < 5; i++)
	{
		std::cin >> structPointer[i].structString;
	}

	for (auto i = 0; i < 5; i++)
	{
		std::cout << structPointer[i].structString << std::endl;
	}

	delete[] structPointer;

}


I guess it isn't too bad for you to learn to get used to using character arrays. We all had to learn how to use them at some point.

Pointers are weird. What a pointer is is something that stores the memory address of something else. So, "structPointer" is holding the memory address of a "myStruct" in memory. If you just do std::cout << structPointer;, you'll get the memory address of the thing. If you want to get the value, you can do this: std::cout << *structPointer.myString;
The * deferences the pointer and you can actually access the values stored in the struct in memory.
The way arrays are handled is really cool.

You can do this: int arr[5];, and it will create an array of 5 integers. But how does it actually get "arr[3]"? When created, arrays are stored in a contiguous block in memory. "arr[0]" is the very first integer in the array, right? So, if you wanted "arr[1]", you just need the integer right after "arr[0]". In other words, you can step over in memory and grab it. It will take your index, and step over in memory to grab the thing your're asking for. It knows how much space your thing takes, so it can determine very quickly how far to step over.

When you do: activity_pointer = new activityInfo [max];, you are creating an array of activityInfo structures in a contiguous block in memory. You can index through them like you would if you just created an array with a fixed size. The only big difference that you need to be worried about is that it is created on the HEAP, not the STACK. Thus, you must delete it when you are done (you do not need to tell C++ how many you are deleting).

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
int main()
{
	int *integerPointer; //Pointer to an integer, but we can use this to dynamically create an array
	int integerArraySize = 0;

	std::cout << "Enter the number of integers in the dynamic array: ";
	std::cin >> integerArraySize;

	if (integerArraySize < 0 || integerArraySize > 20) //Let's keep it small for now
	{
		std::cerr << "Stop trying to break things." << std::endl;
		std::cerr << "An error, you get." << std::endl;
		exit(-3);
	}

	integerPointer = new int[integerArraySize]; //We just created a new array, in a contiguous block in memory.  We can index through this just like a regular array!

	for (auto i = 0; i < integerArraySize; i++)
	{
		integerPointer[i] = pow(i, 2);
	}

	for (auto i = 0; i < integerArraySize; i++)
	{
		std::cout << "We are looking at index: " << i << std::endl;
		std::cout << "\tThe address of integerPointer[" << i << "]" << " is: " << &integerPointer[i] << std::endl;
		std::cout << "\tThe value at integerPointer[" << i << "]" << " is: " << integerPointer[i] << std::endl;
	}

	delete[] integerPointer; //Make sure you delete
return 0;
}


For my machine, an "int" moves by 4 from address to address, but other data types will have different distances between them. For example, changing this to use "long long int" changes the distance between the addresses to be 0x08. Oh, addresses are in hex, by the way. So, don't be freaked out if you see letters in the memory addresses.

Pointers can get much more complex, but I'll let you sit on this for now. It's better to take pointers a little at a time so as to not get overwhelmed.
Last edited on
Wow such a lengthy post. I def. appreciate the time you took to type all of that thanks~

So I was playing around with what you said before about using 'i' to for index incrementation and I finally got something to work:

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
void activities::new_activity()
{
  char response = 'Y';
  int i = 0;

  cout << "Please enter the max number of activities: ";
  cin >> max;

  activity_pointer = new activityInfo [max];

  while(i < max && toupper(response) == 'Y')
  {
     cout << "Please enter in the name of activity: ";
     cin >> activity_pointer[i].activity_name;  //changed -> to [i].
     .
     . 
     .
     i++;
     activity_pointer[i+1];

     cout << "Would you like to enter in another activity (Y/N)? ";
     cin >> response;
     cin.ignore(100,'\n');
   }

   if(i == max)
     cout << "We've reached the max number of activities" << endl; 
     //the above line is to check to make sure i < max is working
}


I'm not sure if I did anything illegal in there or if I did something considered to be bad programming practice but it works for my purposes
It looks good to me, except for one thing:

activity_pointer[i+1];
That line doesn't actually do anything. It just reads the memory and then does nothing with it.
You're also stepping out of bounds right there. If you have MAX elements, then the index of the last element is MAX-1. In the last iteration of your loop "i" is one less than max. Adding one to that will make it at go out of bounds.

You're only reading the memory, not trying to write to it, so it usually won't throw an exception, but you should remove that line anyway, just to be safe. Some classes will throw an exception even if you try to read out of bounds, so just be wary of making sure you stay in bounds of your array.

You might also want to check if the user has already allocated new activities before you allocate more, otherwise you could have some issues. Dynamic allocation comes with its own set of challenges, but also a whole wide range of new possibilities. You just have to be careful when allocating and deleting.

Here's a page on going out of bounds: http://www.cplusplus.com/reference/stdexcept/out_of_range/
Here's a page on dynamic memory: http://www.cplusplus.com/doc/tutorial/dynamic/

Both of those are from this website, of course XD

They go more in depth and explain more of the finer details. They also give some examples and things to watch out for.

Topic archived. No new replies allowed.