Making a vector of type class?

Pages: 12
Hey guys,

I am having quite a bit of trouble making a vector of objects (specifically with using classes). So I was wondering if someone could give me a semi-detailed example based on the following scenario:

Let's say that you have 6 animals to start (2 cats, 2 dogs, 2 fish) and they all reproduce at a certain time (every 9 months for cats, every 12 months for dogs, and every 8 months for fish).

For simplicity, let's say that the constructor that we are using has 3 parameters: type of animal and gender.

How would you 1) add the initial 6 animals to a vector of objects with their species and age and 2) add their offspring to the class with the same attributes as well?

Please keep in mind that I am a beginner in C++, so I do not have a lot of tools in my belt right now.

Last edited on
Something along these lines:

#include <iostream>
#include <vector>
#include <random>

struct animal {

    enum species_t { LION, COW, DOVE, RAVEN, GUPPY, GOLDFISH } ;
    static constexpr const char* const species_text[] = { "lion", "cow", "dove", "raven", "guppy", "goldfish" };

    enum gender_t { MALE, FEMALE } ;
    static constexpr const char* const gender_text[] = { "male", "female" };

    animal() noexcept = default ;
    animal( species_t species, gender_t gender, int age ) noexcept : species_(species), age_(age), gender_(gender) {}

    species_t species() const noexcept { return species_ ; }
    gender_t gender() const noexcept { return gender_ ; }
    int age() const noexcept { return age_ ; }

    private:

        species_t species_ = LION ;
        int age_ = 0 ;
        gender_t gender_ = MALE ;

   friend std::ostream& operator << ( std::ostream& stm, const animal& a ) {

      return stm << "animal{" << animal::species_text[a.species()] << ','
                 << a.age() << ',' << animal::gender_text[a.gender()] << '}' ;
   }
};

std::vector<animal>& add_offsprings( std::vector<animal>& animals, animal::species_t species ) {

    // https://www.stroustrup.com/C++11FAQ.html#for
    // https://www.stroustrup.com/C++11FAQ.html#auto
    for( const animal& a : animals ) { // for each animal in the vector

        if( a.species() == species && a.gender() == animal::FEMALE ) { // if it is a female of the specified species

            #ifndef NDEBUG
                std::cout << "    *** debug: adding offspring for " << a << " ****\n" ;
            #endif // NDEBUG
            // add an offspring (we assume that there would be just one offspring)
            // same species, we choose a gender at random, age == 0
            static std::mt19937 rng( std::random_device{}() ) ;
            // https://en.cppreference.com/w/cpp/container/vector/emplace_back
            animals.emplace_back( species, rng()&1U ? animal::MALE : animal::FEMALE, 0 ) ;
        }
    }

    return animals ;
}

void print( const std::vector<animal>& animals ) {

    for( const animal& a : animals )  std::cout << a << '\n' ;
    std::cout << "----------\n" ;
}

int main() {

    // create a vector with two animals
    std::vector<animal> animals { animal( animal::COW, animal::FEMALE, 3 ), animal ( animal::LION, animal::MALE, 5 ) } ;
    animals.emplace_back( animal::LION, animal::FEMALE, 4 ) ; // add one more animal
    print(animals) ;

    add_offsprings( animals, animal::LION ) ; // add offsprings for female lions
    print(animals) ;
}

http://coliru.stacked-crooked.com/a/f7ebe8126e8315fe
Thank you, JLBorges. This was actually super helpful. I also appreciate you including the links and comments for me to easily follow along. Since I am a newbie, I was wondering if you could explain this line to me:

friend std::ostream& operator << ( std::ostream& stm, const animal& a )

I have never seen "friend" before in code so it was a bit confusing.

Also, I see that lines 44-45 assume we are only adding one offspring of a certain species. How would one modify that if the new addition were expected to reproduce in the same time frame as well? So if there are two female lions, we will have two additional lion offspring when it is their time again.
Last edited on

I have never seen "friend" before in code so it was a bit confusing.


Notice in the code snippets, keywords are blue in colour. So one can look them up the reference. It is a good skill to do ones own research.

https://www.cplusplus.com/doc/tutorial/inheritance/

https://en.cppreference.com/w/cpp/language/friend
Last edited on
> friend std::ostream& operator << ( std::ostream& stm, const animal& a )

It is an oveloaded stream insertion operator:
https://en.cppreference.com/w/cpp/language/operators#Stream%20extraction_and_insertion
" Since they take the user-defined type as the right argument (b in a@b), they must be implemented as non-members."

Declaring the function as a friend: grants the non-member function access to the non-public members of the class.
More information: https://en.cppreference.com/w/cpp/language/friend


> How would one modify that if the new addition were expected to reproduce in the same time frame as well?
> So if there are two female lions, we will have two additional lion offspring when it is their time again.

We can call the function add_offsprings once again when the time arrives.
For example:
1
2
3
4
5
6
7
8
9
10
11
    int curr_time = 0 ;
    const int offspring_interval_lion = 7 ;
    while( animals.size() < 10 ) { // till seven more lions are added

        curr_time += offspring_interval_lion ; // simulate passage of time
        for( animal& a : animals ) a.advance_age_by(offspring_interval_lion) ;
        
        std::cout << "time: " << curr_time << '\n' ;
        add_offsprings( animals, animal::LION ) ; // add offsprings for female lions
        print(animals) ;
    }

http://coliru.stacked-crooked.com/a/a6772820bf252c48
Oops, you're right. I could have just looked that up haha. I'm sorry about that.

Thank you for the help once again, JLBorges.
1
2
3
4
for( const animal& a : animals ) {
      //...
            animals.emplace_back( species, rng()&1U ? animal::MALE : animal::FEMALE, 0 ) ; //¿?
    }
don't modify the container that you're iterating with a range-based for
adding elements may cause a reallocation, then your iterators become invalid, then you are reading garbage
alternatives:
1
2
3
4
5
int n = animals.size;
for (int K=0; K<n; ++K){ // old for
  //...
  animals.emplace_back(/**/); // now there is no problem modifying the container
}

1
2
3
4
5
6
std::vector<animal> offspring; // use an auxiliar
for (const animal &a: animals){
  //...
  offspring.emplace_back( /**/ ); // modify the auxiliar
}
animals.insert(animals.end(), offspring.begin(), offspring.end()); // merge the auxiliar 




1
2
if( a.species() == species && a.gender() == animal::FEMALE )
// and a.age() > 18 and a.age() < 56 and a.last_birth() > 12 and ... 

move that logic to the object
1
2
if (a.on_heat())
  auto offspring = a.breed(); //¡yay, mitosis! 

Last edited on
Another way to account for offspring is to have each animal specify who its parents are instead of the parent keeping a vector of all it's children, just like a human birth certificate.
I have been working on this snippet of code for a while and have run into some errors. I am attempting to simplify the code that JL wrote so it is easier for me to understand and I do not know how to fix one error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class Animal {
public:
    enum species_t {LION, FOX, RAVEN, HAWK, TETRA, SALMON}; //stores different species
    enum gender_t {MALE, FEMALE}; //stores different genders

    //default constructor
    Animal() {

    }
    //overloaded constructor
    Animal(species_t species, gender_t gender, int age) {

    }

    //create getInfo funcs???

    bool inHeat(vector<Animal>& animals) {
        for (Animal& ani : animals) {
            if ((ani.species() == Animal::SALMON || ani.species() == Animal::TETRA) && ani.gender() == Animal::FEMALE) {//checks for fish & female
                if (months % 6 == 0 && months != 0) { //checks to see if fish is in heat
                    return true;
                }
            }
            if ((ani.species() == Animal::HAWK || ani.species() == Animal::RAVEN) && ani.gender() == Animal::FEMALE) {//checks for bird & female
                if (months % 9 == 0 && months != 0) { //checks to see if bird is in heat
                    return true;
                }
            }
            if ((ani.species() == Animal::FOX || ani.species() == Animal::LION) && ani.gender() == Animal::FEMALE) {//checks for mammal & female
                if (months % 12 == 0 && months != 0) { //checks to see if mammal is in heat
                    return true;
                }
            }
            return false;
        }
    }
    void ageUp(const vector<Animal>& animals) {
        for (const Animal& ani : animals) {
            //add 1 month to all ages
        }
    }

    species_t species() const noexcept {
        return species;
    }

    gender_t gender() const noexcept {
        return gender;
    }

    int age() const noexcept {
        return age;
    }

private:
    int age = 0, months = 11;
    species_t species = LION;
    gender_t gender = MALE;
        
};

int main() {
    vector<Animal> animals{ Animal(Animal::LION, Animal::FEMALE, 11), //the first 12 animals @ starting ages of 11 mths
                                 Animal(Animal::LION, Animal::MALE, 11),
                                 Animal(Animal::FOX, Animal::FEMALE, 11),
                                 Animal(Animal::FOX, Animal::MALE, 11),
                                 Animal(Animal::HAWK, Animal::FEMALE, 11),
                                 Animal(Animal::HAWK, Animal::MALE, 11),
                                 Animal(Animal::RAVEN, Animal::FEMALE, 11),
                                 Animal(Animal::RAVEN, Animal::MALE, 11),
                                 Animal(Animal::TETRA, Animal::FEMALE, 11),
                                 Animal(Animal::TETRA, Animal::MALE, 11),
                                 Animal(Animal::SALMON, Animal::FEMALE, 11),
                                 Animal(Animal::SALMON, Animal::MALE, 11) };

    return 0;
}


I am getting errors near my returning functions on lines 43-51 (species(), gender(), and age()) whenever I want to return just the variable. Those variables are defined in private so I do not know what could be wrong.

I am also getting an error where the "ani" portion of ani.gender() and ani.species() is underlined. I thought I fixed this issue earlier but I guess not.

(Note: I am still rewriting some of the code and testing in pieces so I may be missing something minor that I overlooked. If so, please point it out.)

Edit: Removed previous edits to a separate comment as per George's advice and set code back to its original state.
Last edited on
One problem is that you have functions with the name of variables - age, species, gender
Clearly, I am an idiot for not noticing that. Such a simple fix. Thank you, thmm. There is another issue though that I have added as an edit for my previous post. Any chance you could help me out with that?
1
2
ani.species()
ani.gender()


species and gender are not functions.
Last edited on
@Peter87 Yeah, I realized that a little while after I made the edit but forgot to update. I changed my function names so I needed to change them in the ani.species() too.

I have updated my previous post now for any future viewers. Thank you all so much for the help and your patience :)
Please don't update a post that shows code issues, add another to show updated code. Now all the responses that correct your initial code look to be wrong.

You learned from your mistakes, leaving the code as it originally was with updates lets others learn as well.
That's a good point. Fortunately, the issues were minor and easy to restore so I have set the code to its original state and added the new code here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class Animal {
public:
    enum species_t {LION, FOX, RAVEN, HAWK, TETRA, SALMON}; //stores different species
    enum gender_t {MALE, FEMALE}; //stores different genders

    //default constructor
    Animal() {

    }
    //overloaded constructor
    Animal(species_t species, gender_t gender, int age) {

    }

    //create getInfo funcs???

    bool inHeat(vector<Animal>& animals) {
        for (Animal& ani : animals) {
            if ((ani.specie() == Animal::SALMON || ani.specie() == Animal::TETRA) && ani.gende() == Animal::FEMALE) {//checks for fish & female
                if (months % 6 == 0 && months != 0) { //checks to see if fish is in heat
                    return true;
                }
            }
            if ((ani.specie() == Animal::HAWK || ani.specie() == Animal::RAVEN) && ani.gende() == Animal::FEMALE) {//checks for bird & female
                if (months % 9 == 0 && months != 0) { //checks to see if bird is in heat
                    return true;
                }
            }
            if ((ani.specie() == Animal::FOX || ani.specie() == Animal::LION) && ani.gende() == Animal::FEMALE) {//checks for mammal & female
                if (months % 12 == 0 && months != 0) { //checks to see if mammal is in heat
                    return true;
                }
            }
            return false;
        }
    }
    void ageUp(const vector<Animal>& animals) {
        for (const Animal& ani : animals) {
            //add 1 month to all ages
        }
    }

    species_t specie() const noexcept {
        return species;
    }

    gender_t gende() const noexcept {
        return gender;
    }

    int ag() const noexcept {
        return age;
    }

private:
    int age = 0, months = 11;
    species_t species = LION;
    gender_t gender = MALE;
        
};

int main() {
    vector<Animal> animals{ Animal(Animal::LION, Animal::FEMALE, 11), //the first 12 animals @ starting ages of 11 mths
                                 Animal(Animal::LION, Animal::MALE, 11),
                                 Animal(Animal::FOX, Animal::FEMALE, 11),
                                 Animal(Animal::FOX, Animal::MALE, 11),
                                 Animal(Animal::HAWK, Animal::FEMALE, 11),
                                 Animal(Animal::HAWK, Animal::MALE, 11),
                                 Animal(Animal::RAVEN, Animal::FEMALE, 11),
                                 Animal(Animal::RAVEN, Animal::MALE, 11),
                                 Animal(Animal::TETRA, Animal::FEMALE, 11),
                                 Animal(Animal::TETRA, Animal::MALE, 11),
                                 Animal(Animal::SALMON, Animal::FEMALE, 11),
                                 Animal(Animal::SALMON, Animal::MALE, 11) };

    return 0;
}


**Code after mistakes were rectified. Only function names needed to be changed.
Last edited on
bool inHeat(vector<Animal>& animals)
an Animal should represent just an animal, not a collection of animals
the function should be coded as working on the current object only
1
2
3
4
5
6
7
8
9
10
11
12
bool inHeat() const{
   if (this->species == SALMON
       and this->gender == FEMALE
       and this->months > 0
       and this->months % 6 == 0
      ){
     return true;
   }
   if (this->species == HAWK /*... */)
   //...
   return false
}
then in your main function you may do
1
2
3
4
5
for(const auto &a: animals){
  if (a.inHeat()){
    //...
  }
}



you may note that the inHeat() function will get quite big as you add new species, ¿have you learned about polymorphism and inheritance?
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
class Animal{
  public:
    virtual bool inHeat() const = 0;
    virtual bool ~Animal() = default
}

class Amoeba: public Animal{
  public:
    virtual bool inHeat() const override{
      return true;
    }
}

class Snail: public Animal{
  public:
    virtual bool inHeat() const override{
      return this->isFemale();
    }
}

class Hawk: public Animal{
  public:
    virtual bool inHeat() const override{
      return this->isFemale()
        and this->age() > 13
        and this->last_birth() > 4;
    }
}
so you encapsulate the inHeat() definition in each different class

keep in mind that you can't store Animal objects now, need to create pointers instead (may also play with the strategy pattern)
std::vector< std::unique_ptr<Animal> > animals;
Last edited on
@ne555 Thank you for the reply. I have now updated my bool inHeat() function and I think it looks really good:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool inHeat() {//checks every animal to see if they meet the requirements for reproduction
        if (this->species == SALMON && this->gender == FEMALE && months > 0 && months % 6 == 0) {
            return true;
        }
        if (this->species == TETRA && this->gender == FEMALE && months > 0 && months % 6 == 0) {
            return true;
        }
        if (this->species == HAWK && this->gender == FEMALE && months > 0 && months % 9 == 0) {
            return true;
        }
        if (this->species == RAVEN && this->gender == FEMALE && months > 0 && months % 9 == 0) {
            return true;
        }
        if (this->species == FOX && this->gender == FEMALE && months > 0 && months % 12 == 0) {
            return true;
        }
        if (this->species == LION && this->gender == FEMALE && months > 0 && months % 12 == 0) {
            return true;
        }
        return false;
    }


I only need 6 animals in this case so fortunately no need for subclasses.

I did want to know how one would use a pointer to access a method of a class though. I am breaking down the code that I currently have into tiny pieces in order to find all errors and I ran across this one:

no suitable conversion from "Animal" to "Animal(*)(Animal::species_t species, Animal::gender_t gender, int age)" exists

As previously stated, I am not very good with pointers, especially outside of pointing to integers. This is what I have so far:

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
class Animal {
public:
    //enumerators
    enum species_t {LION, FOX, HAWK, RAVEN, TETRA, SALMON};
    enum gender_t {MALE, FEMALE};

    //base constructor
    Animal() {

    }

    //overloaded constructor
    Animal(species_t species, gender_t gender, int age) {

    }

    void addAnimal(Animal(species_t species, gender_t gender, int age)) {
        animals.push_back(Animal(species, gender, age));
    }

    //getInfo funcs

    species_t specie() {
        return species;
    }

    gender_t gende() {
        return gender;
    }

    int ag() {
        return age;
    }

    //print func
    void printVec(vector<Animal>& myAnimals) {
        for (int i = 0; i < myAnimals.size(); i++) {
            cout << "Animal #" << i + 1 << myAnimals[i].specie() << " " << myAnimals[i].gende() << " " << myAnimals[i].ag();
        }
    }

private:
    //member vars
    int age = 0;
    species_t species = LION;
    gender_t gender = MALE;
    vector<Animal> animals;
};

int main() {
    Animal myAnimals;
    myAnimals.addAnimal(Animal(Animal::LION, Animal::MALE, 11));
    return 0;
}


The error occurs in the main function (line 52) and I am fairly sure that a pointer is necessary, but I do not know how to go about implementing it.
Last edited on
1) The signature of Animal::addAnimal() isn't right, and, possibly, nor is the definition.

If you want the method to take the values of the properties of the new animal, and to take responsibility for creating a new Animal object, it should be:

17
18
19
    void addAnimal(species_t species, gender_t gender, int age) {
        animals.push_back(Animal(species, gender, age));
    }


Alternatively, if you want the method to simply take an Animal object and add it to the list - which is how you seem to want to use it in main(), it's simply:

17
18
19
    void addAnimal(const& Animal anAnimal) {
        animals.push_back(anAnimal);
    }


2) A much bigger problem is that your class design seems really muddled. Animal seems to represent a single animal - one age, one species, one gender - and yet it also contains a list of animals.

Why? Why does one animal need to also own a list of other animals?
Pages: 12