Forwarding constructors

Pages: 12
Normally when inheriting you have to write in the arguments to the bases constructor in the derived class, is there any way to make this easier? for example I have:

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
#include <iostream>
#include <memory>

class Animal
{
    public:
        Animal(const std::string& name, double weight) : mName(name), mWeight(weight)
        {}

    private:
        std::string mName{ "Animal Name" };
        double mWeight{ 0.0 };
};

class Cat : public Animal
{
    public:
        Cat();

    private:

};


int main()
{


    return 0;
}


and normally you would have to do this:

1
2
Cat(const std::string& name, double weight) : Animal{ name, weight }
{}


But this can become tedious when you want to update the base class you have to update all the derived classes and its just a lot of typing, there has to be an easier way right? Is there any way to forward the constructor?
Inherit the base class constructors like this:
class Cat : public Animal { using Animal::Animal; };
This is a C++11 feature.
https://en.cppreference.com/w/cpp/language/using_declaration
Last edited on
Ok thank you, like this?

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <iostream>
#include <memory>

class Animal
{
    public:
        Animal
        (
            const std::string& name, 
            const std::string& gender, 
            const std::string type, 
            const std::string& pronoun, 
            double weight) 
            : 
            mName(name), 
            mGender(gender), 
            mType(type), 
            mPronoun(pronoun), 
            mWeight(weight)
        {}

        virtual ~Animal() = default;

        virtual void Speak() const
        {
            std::cout << "Default Speak\n";
        }

        std::string GetName() const
        {
            return mName;
        }

        double GetWeight() const
        {
            return mWeight;
        }

        std::string GetGender() const
        {
            return mGender;
        }

        std::string GetType() const
        {
            return mType;
        }

        std::string GetPronoun() const
        {
            return mPronoun;
        }

    private:
        std::string mName{ "Animal Name" };
        std::string mGender{ "None" };
        std::string mType{ "Animal Type" };
        std::string mPronoun{ "Animal Pronoun" };
        double mWeight{ 0.0 };
};

class Cat : public Animal
{
    public:
        using Animal::Animal;

        void Speak() const override
        {
            std::cout << "Meow.\n";
        }

    private:
};

class Dog : public Animal
{
    public:
        using Animal::Animal;

        void Speak() const override
        {
            std::cout << "Bark\n";
        }

    private:
};

void CheckForNull(std::unique_ptr<Animal>& animal)
{
    if (animal == nullptr)
    {
        std::cout << "Pointer has been reset to null!\n";
    }
    else
    {
        std::cout << "This animal is a " << animal->GetGender() << " " << animal->GetType() << " named " << animal->GetName();
        std::cout << " and " << animal->GetPronoun() << " says ";
        animal->Speak();

        std::cout << animal->GetName() << " weighs " << animal->GetWeight() << " lbs.\n\n";
    }
}

int main()
{
    std::unique_ptr<Animal> wiskers{ std::make_unique<Cat>("Wiskers", "Male", "Cat", "He", 5) };
    std::unique_ptr<Animal> fido{ std::make_unique<Dog>("Fido", "Female", "Dog", "She", 20) };


    //wiskers.reset();

    CheckForNull(wiskers);
    CheckForNull(fido);

    return 0;
}


But if I wanted to add say, a lives variable to Cat, i would still have to do:

1
2
3
4
5
6
7
8
9
10
Cat(
            const std::string& name, 
            const std::string& gender, 
            const std::string type, 
            const std::string& pronoun, 
            double weight, 
            int lives) 
            : 
            Animal{name, gender, type, pronoun, weight}, mLives(lives)
{}


??
Last edited on
But if I wanted to add say, a lives variable to Cat, i would still have to [...]

Right, and, if you leave the using Animal::Animal in there, the original constructors from the base class would still be usable, even though they don't incorporate the lives variable.

On an unrelated note, consider changing this
void CheckForNull(std::unique_ptr<Animal>& animal)
to this:
void CheckForNull(Animal const* animal)
Following the rationale in the Core Guidelines R.33 and R.30 (and elsewhere)
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r33-take-a-unique_ptrwidget-parameter-to-express-that-a-function-reseats-the-widget
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r30-take-smart-pointers-as-parameters-only-to-explicitly-express-lifetime-semantics
Ah ok, yeah that makes more sense to pass a raw pointer, thank you, although i prefer const Animal* animal syntax as its more consistent with how i write my code.

Another question, is there any way to use a function from a derived class? I know that the reason i cant is because its a pointer to an animal object not a cat object which is why i cant, but there has to be a way right? The only way I know of is downcasting, which i dont know how to do but I heard thats the only way to use a function in a derived class when you use a pointer to a base class. Here is my existing updated 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
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include <iostream>
#include <memory>

//TODO
//
//Figure out way to use derived class functions without downcasting.

class Animal
{
    public:
        Animal
        (
            const std::string& name, 
            const std::string& gender, 
            const std::string type, 
            const std::string& pronoun, 
            double weight) 
            : 
            mName(name), 
            mGender(gender), 
            mType(type), 
            mPronoun(pronoun), 
            mWeight(weight)
        {}

        virtual ~Animal() = default;

        virtual void Speak() const
        {
            std::cout << "Default Speak\n";
        }

        virtual void AdditionalInfo() const
        {
            std::cout << "Default Info\n";
        }

        std::string GetName() const
        {
            return mName;
        }

        double GetWeight() const
        {
            return mWeight;
        }

        std::string GetGender() const
        {
            return mGender;
        }

        std::string GetType() const
        {
            return mType;
        }

        std::string GetPronoun() const
        {
            return mPronoun;
        }

    private:
        std::string mName{ "Animal Name" };
        std::string mGender{ "None" };
        std::string mType{ "Animal Type" };
        std::string mPronoun{ "Animal Pronoun" };
        double mWeight{ 0.0 };
};

class Cow : public Animal
{
    public:
        using Animal::Animal;

        void Speak() const override
        {
            std::cout << "Moo\n";
        }
};

class Dog : public Animal
{
    public:
        using Animal::Animal;

        void Speak() const override
        {
            std::cout << "Bark\n";
        }
};

class Cat : public Animal
{
    public:
        Cat
        (
            const std::string& name,
            const std::string& gender,
            const std::string type,
            const std::string& pronoun,
            double weight,
            int lives
        )
            :
            Animal{name, gender, type, pronoun, weight}, mLives(lives)
        {}

        void Speak() const override
        {
            std::cout << "Meow.\n";
        }

        void AdditionalInfo() const override
        {
            std::cout << GetName() << " also has " << mLives << " lives!" << "\n\n";
        }

        void LandOnFeet()
        {
            std::cout << "When " << GetName() << " falls " << GetPronoun() << " always lands feet first!\n";
        }

    private:
        int mLives{ 9 };
};

void CheckForNull(const Animal* animal)
{
    if (animal == nullptr)
    {
        std::cout << "Pointer has been reset to null!\n";
    }
    else
    {
        std::cout << "This animal is a " << animal->GetGender() << " " << animal->GetType() << " named " << animal->GetName();
        std::cout << " and " << animal->GetPronoun() << " says ";
        animal->Speak();

        std::cout << animal->GetName() << " weighs " << animal->GetWeight() << " lbs.\n";
    }
}

int main()
{
    std::unique_ptr<Animal> wiskers{ std::make_unique<Cat>("Wiskers", "Male", "Cat", "He", 5, 9) };
    std::unique_ptr<Animal> fido{ std::make_unique<Dog>("Fido", "Female", "Dog", "She", 20) };
    std::unique_ptr<Animal> bessie{ std::make_unique<Cow>("Bessie", "Female", "Cow", "She", 900) };


    //wiskers.reset();

    CheckForNull(wiskers.get());
    wiskers->AdditionalInfo();
    wiskers->LandOnFeet() // Cant do this

    CheckForNull(fido.get());
    CheckForNull(bessie.get());

    return 0;
}
Last edited on
You can do this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
    std::unique_ptr<Cat> wiskers{ std::make_unique<Cat>("Wiskers", "Male", "Cat", "He", 5, 9) };
    std::unique_ptr<Dog> fido{ std::make_unique<Dog>("Fido", "Female", "Dog", "She", 20) };
    std::unique_ptr<Cow> bessie{ std::make_unique<Cow>("Bessie", "Female", "Cow", "She", 900) };


    //wiskers.reset();

    CheckForNull(wiskers.get());
    wiskers->AdditionalInfo();
    wiskers->LandOnFeet(); // Cant do this

    CheckForNull(fido.get());
    CheckForNull(bessie.get());

    return 0;
}
Figure out way to use derived class functions without downcasting


Another way is to change the design to have LandOnFeet() a member of the base class which defaults to displaying an error message. Cat then just overrides this function as desired.
For downcasting, consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main() {
	std::unique_ptr<Animal> wiskers{ std::make_unique<Cat>("Wiskers", "Male", "Cat", "He", 5, 9) };
	std::unique_ptr<Animal> fido{ std::make_unique<Dog>("Fido", "Female", "Dog", "She", 20) };
	std::unique_ptr<Animal> bessie{ std::make_unique<Cow>("Bessie", "Female", "Cow", "She", 900) };

	//wiskers.reset();

	CheckForNull(wiskers.get());
	wiskers->AdditionalInfo();

	if (const auto dc { dynamic_cast<Cat*>(wiskers.get()) }; dc)
		dc->LandOnFeet();
	else
		std::cout << "Invalid Cat cast\n";

	if (const auto dc { dynamic_cast<Cat*>(fido.get()) }; dc)
		dc->LandOnFeet();
	else
		std::cout << "Invalid Cat cast\n";

	CheckForNull(fido.get());
	CheckForNull(bessie.get());
}


See:
https://en.cppreference.com/w/cpp/language/dynamic_cast
I would say keep the type as long as possible. Using [dynamic_]cast may hint at a design flaw...
A pointer to a derived class is a valid pointer to a base class.

So one can make a std::vector of pointer to Animal, then populate it with pointers to Cat, Dog, Cow etc. Then loop through the vector calling Speak function, it will call the correct function for each animal. This is one of the main strengths of virtual polymorphism, and is a powerful feature. No need for casting.
... assuming the required function is part of the base class. For LandOnFeet() it is defined only for the derived Cat class. So polymorphism won't work in this case. The other functions are defined in the base class and the derived classes have overwrite functions so in these cases polymorphism works great.
seeplus wrote:
... assuming the required function is part of the base class.


Yes, and I should have quoted this bit from the OP:

Ch1156 wrote:
Another question, is there any way to use a function from a derived class? I know that the reason i cant is because its a pointer to an animal object not a cat object which is why i cant, but there has to be a way right? The only way I know of is downcasting, which i dont know how to do but I heard thats the only way to use a function in a derived class when you use a pointer to a base class.


I guess it's a common thing for people to think they have to downcast, instead of using virtual functions how they are supposed to be used.
You can do this:


But doesnt this ruin the whole polymorphism concept? Without polymorphism i am allowed to use the functions in base and in derived, but with virtual functions and a pointer to base i cannot do that it seems.

Making LandOnFeet() part of the animal class isnt something i want to do since this behavior's is specific to cats so putting it in Animal is strange. even if i were to just have the function default to saying "This animal cannot land on its feet" to me it still seems like a waste since that one function can only be used with a Cat.

It seems strange to me that this is not allowed in polymorphism, as thats the entire point of inheritance, common functionality goes in the base class, derived classes are going to have functionality specific to them. and If i have to downcast every function that's unique to that class then that defeats the purpose of polymorphism it seems, but perhaps im missing something, or not understanding correctly.

Right now i only have 1 function specific to Cat, but lets say for example, what if i had 10? or 15 functions that were specific to that class only and had no place in the base class? now im downcasting 15 different functions.

I think downcasting is fine for this specific case where i have just one function, but i cant help but wonder how i would deal with many different unique functions.
Last edited on
I think the purpose of polymorphism is to be able to provide a common interface that is shared between multiple types but where the behaviour (implementation) might differ.
Ch1156 wrote:
It seems strange to me that this is not allowed in polymorphism, as thats the entire point of inheritance, common functionality goes in the base class, derived classes are going to have functionality specific to them. and If i have to downcast every function that's unique to that class then that defeats the purpose of polymorphism it seems, but perhaps im missing something, or not understanding correctly.


Instead of common and specific functionality, think instead of common and specific behavior. This means the function names are the same, as in Speak(), but the behavior is different for each type.

When doing virtual polymorphism, it's usually a good idea of thinking of things in a generic way. One reason is that one usually wants to loop through a container, and apply the same function to all the items in the container.

I don't see anything wrong with a function in the base class:

bool LandsOnFeet();

The default behavior could be to return false, Cat overrides it to return true.

Ch1156 wrote:
now im downcasting 15 different functions.


No, you downcast once for the Cat, then use that pointer to call any function that belongs to Cat.

One doesn't have to have all the default behavior in the base class. Imagine an inheritance tree for the Animal Kingdom: there are mammals, reptiles, birds, monotremes, fish etc. These can all be derived from Animal and have properties and functions that are specific to that whole group. Then there would be further derived classes like Human which derives from Mammal. The Animal base class would have very generic things like weight which is common to all the derived classes.

Just to reiterate: Make a container of pointer to Animal, and populate it with pointers to the derived class objects. Downcasting (if necessary) one of these pointers should be easy because it was a pointer to derived to start with.

Edit: I have very much simplified the idea of the Animal Kingdom.
Last edited on
I don't see anything wrong with a function in the base class:

bool LandsOnFeet();

The default behavior could be to return false, Cat overrides it to return true.


But the way i see it is that no other animal will use that behavior, I see the base Animal class as something that contains only behavior that is common to all animals, and if the cat is the only animal in the world that has that behavior it doesnt seem right to put it in the base class, at least thats how i see it. Perhaps im just focusing too much on the semantics of it idk.

I will try the container thing next, how about something like this for now?

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#include <iostream>
#include <memory>

//TODO
//
//Figure out way to use derived class functions without downcasting.

class Animal
{
    public:
        Animal
        (
            const std::string& name, 
            const std::string& gender, 
            const std::string type, 
            const std::string& pronoun, 
            double weight) 
            : 
            mName(name), 
            mGender(gender), 
            mType(type), 
            mPronoun(pronoun), 
            mWeight(weight)
        {}

        virtual ~Animal() = default;

        virtual void Speak() const
        {
            std::cout << "Default Speak\n";
        }

        virtual void AdditionalInfo() const
        {
            std::cout << "Default Info\n";
        }

        std::string GetName() const
        {
            return mName;
        }

        double GetWeight() const
        {
            return mWeight;
        }

        std::string GetGender() const
        {
            return mGender;
        }

        std::string GetType() const
        {
            return mType;
        }

        std::string GetPronoun() const
        {
            return mPronoun;
        }

    private:
        std::string mName{ "Animal Name" };
        std::string mGender{ "None" };
        std::string mType{ "Animal Type" };
        std::string mPronoun{ "Animal Pronoun" };
        double mWeight{ 0.0 };
};

class Cow : public Animal
{
    public:
        using Animal::Animal;

        void Speak() const override
        {
            std::cout << "Moo\n";
        }
};

class Dog : public Animal
{
    public:
        using Animal::Animal;

        void Speak() const override
        {
            std::cout << "Bark\n";
        }
};

class Cat : public Animal
{
    public:
        Cat
        (
            const std::string& name,
            const std::string& gender,
            const std::string type,
            const std::string& pronoun,
            double weight,
            int lives
        )
            :
            Animal{name, gender, type, pronoun, weight}, mLives(lives)
        {}

        void Speak() const override
        {
            std::cout << "Meow.\n";
        }

        void AdditionalInfo() const override
        {
            std::cout << GetName() << " also has " << mLives << " lives!" << "\n\n";
        }

        void LandOnFeet() const
        {
            std::cout << "When " << GetName() << " falls " << GetPronoun() << " always lands feet first!\n";
        }

    private:
        int mLives{ 9 };
};

void CheckForNull(const Animal* animal)
{
    if (animal == nullptr)
    {
        std::cout << "Pointer has been reset to null!\n";
    }
    else
    {
        std::cout << "This animal is a " << animal->GetGender() << " " << animal->GetType() << " named " << animal->GetName();
        std::cout << " and " << animal->GetPronoun() << " says ";
        animal->Speak();

        std::cout << animal->GetName() << " weighs " << animal->GetWeight() << " lbs.\n";
    }
}

//Have this function peer reviewed for accuracy
template <typename Derived>
void AccessDerivedMember(const Animal* animal)
{
    if (const auto dc{ dynamic_cast<const Derived*>(animal) }; dc)
    {
        dc->LandOnFeet();
    }
    else
    {
        std::cout << "Invalid cast.\n\n";
    }
}

int main()
{
    std::unique_ptr<Animal> wiskers{ std::make_unique<Cat>("Wiskers", "Male", "Cat", "He", 5, 9) };
    std::unique_ptr<Animal> fido{ std::make_unique<Dog>("Fido", "Female", "Dog", "She", 20) };
    std::unique_ptr<Animal> bessie{ std::make_unique<Cow>("Bessie", "Female", "Cow", "She", 900) };

    //wiskers.reset();

    CheckForNull(wiskers.get());
    wiskers->AdditionalInfo();

    AccessDerivedMember<Cat>(wiskers.get());
    AccessDerivedMember<Cat>(fido.get()); //Will cause an invalid cast


    CheckForNull(fido.get());
    CheckForNull(bessie.get());

    return 0;
}
Last edited on
Consider making all the other functions in Animal virtual as well. Then the derived classes can call them too.

Ch1156 wrote:
But the way i see it is that no other animal will use that behavior, I see the base Animal class as something that contains only behavior that is common to all animals, and if the cat is the only animal in the world that has that behavior it doesnt seem right to put it in the base class, at least thats how i see it. Perhaps im just focusing too much on the semantics of it idk.


As I said earlier, one needs to think generically. You already have a function:

void AdditionalInfo() const override

Which is a good generic function, although the name DisplayInfo might be better, and have it display all the information.

Consider having that function call LandOnFeet() but only in the Cat class. That way other animals can have specific information manipulated or displayed. No need for the downcasting if you do that.

Here's another example: In a CAD program like AutoCAD there will be a DrawingEntity base class. There are all kinds of Drawing Entities : points, lines, polylines, dimensions, hatch, text, circles, arcs etc. THe virtual functions could be: draw, move, copy, rotate, stretch, mirror. The implementation of all of these functions will be rather different for each type of entity: a hatch is rather different to a dimension for example. If stretching or mirroring for certain things like hatch and dimension are to be disallowed, then these specific functions can throw an exception which could show an error message on screen.
But the way i see it is that no other animal will use that behavior,

If you are creating an API for all animals, then it is not helpful to then think about just cats. Instead of describing just the ability that cats have, you could create a generic function called superPower() in the base class. Then that could be used for any animal:
Animal: Moves around and turns Oxygen into Carbon Dioxide
Cat: lands on feet
Cow: Farts flammable gas
Camel: Always angry
Chicken: smarter than turkeys.
Turkey: Gobble gobble: It literally asks you to eat it.

On the other hand, maybe you have a vector<Cat*> felines of animals that specifically contains the cat branch of the animal kingdom. In this case, maybe you do want landOnFeet(). Lions, cheetahs, ocelots, etc might all inherit from the cat class and therefore share this trait. Since you created this vector in the knowledge that only cat-inherited objects are contained therein, you can safely call landOnFeet() for each object.
Last edited on
Ok so i made some changes, hows this: I didnt make all functions virtual but i figured id get this looked at first before making some more changes. Theres no downcasting now, everythings in the base class.

However, what things would be specific to a cat, something that definitely does not fit in the animal class and is unique to Cat? The reason i want to know this is simply for practice purposes, since im sure I will eventually come across this situation when im programming my game, id like to know what do do in that situation when it arises. I mean, clearly i could just downcast it, but id like an example of something thats unique only to cat or felines in general.

Actually now that i look at it, a function named GetLives() would be unique to that i think, since the saying is that cats have 9 lives, thats unique only to them.

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#include <iostream>
#include <memory>

class Animal
{
    public:
        enum class EyeColor { White, Gray, Yellow, Blue, Brown };

        Animal
        (
            const std::string& name, 
            const std::string& gender, 
            const std::string type, 
            const std::string& pronoun, 
            double weight,
            EyeColor eyeColor
        ) 
            : 
            mName(name), 
            mGender(gender), 
            mType(type), 
            mPronoun(pronoun), 
            mWeight(weight),
            mEyeColor(eyeColor)
        {}

        virtual ~Animal() = default;

        virtual void Speak() const
        {
            std::cout << "Default Speak\n";
        }

        virtual void AdditionalInfo() const
        {
            std::cout << "Default Info\n";
        }

        virtual void Move() const
        {
            std::cout << "Generic animal Movement\n";
        }

        virtual void Fall() const
        {
            std::cout << "Animal falls normally.\n";
        }

        std::string GetName() const
        {
            return mName;
        }

        double GetWeight() const
        {
            return mWeight;
        }

        std::string GetGender() const
        {
            return mGender;
        }

        std::string GetType() const
        {
            return mType;
        }

        std::string GetPronoun() const
        {
            return mPronoun;
        }

        EyeColor GetEyeColor() const
        {
            return mEyeColor;
        }

    private:
        std::string mName{ "Animal Name" };
        std::string mGender{ "None" };
        std::string mType{ "Animal Type" };
        std::string mPronoun{ "Animal Pronoun" };
        double mWeight{ 0.0 };
        EyeColor mEyeColor{ EyeColor::Gray };
};

class Cow : public Animal
{
    public:
        using Animal::Animal;

        void Speak() const override
        {
            std::cout << "Moo.\n";
        }

        void Move() const override
        {
            std::cout << "Cows like " << GetName() << " walk slowly on 4 hooves.\n";
        }

        void AdditionalInfo() const override
        {
            std::cout << "Cows like " << GetName() << " like to graze for grass.\n";
        }
};

class Dog : public Animal
{
    public:
        using Animal::Animal;

        void Speak() const override
        {
            std::cout << "Bark.\n";
        }

        void Move() const override
        {
            std::cout << "Dogs like " << GetName() << " walk around on 4 paws.\n";
        }

        void AdditionalInfo() const override
        {
            std::cout << "Dogs like " << GetName() << " like to play fetch.\n";
        }
};

class Cat : public Animal
{
    public:
        Cat
        (
            const std::string& name,
            const std::string& gender,
            const std::string type,
            const std::string& pronoun,
            double weight,
            int lives,
            EyeColor eyeColor
        )
            :
            Animal{name, gender, type, pronoun, weight, eyeColor}, mLives(lives)
        {}

        void Speak() const override
        {
            std::cout << "Meow.\n";
        }

        void AdditionalInfo() const override
        {
            std::cout << "Cats like " << GetName() << " have " << mLives << " lives!" << "\n";
        }

        void Fall() const override
        {
            std::cout << "When cats like " << GetName() << " fall, they always land feet first!\n";
        }

        void Move() const override
        {
            std::cout << "Cats like " << GetName() << " walk around on 4 paws and can run fast.\n";
        }

    private:
        int mLives{ 9 };
};

std::ostream& operator<<(std::ostream& os, Animal::EyeColor eyeColor)
{
    switch (eyeColor)
    {
        case Animal::EyeColor::White: 
        {
            os << "White";
            break;
        }

        case Animal::EyeColor::Gray: 
        {
            os << "Gray";
            break;
        }

        case Animal::EyeColor::Yellow: 
        {
            os << "Yellow";
            break;
        }

        case Animal::EyeColor::Blue: 
        {
            os << "Blue";
            break;
        }

        case Animal::EyeColor::Brown:
        {
            os << "Brown";
            break;
        }

        default:
            std::cout << "Error\n";

    }
    return os;
}

void CheckForNull(const Animal* animal)
{
    if (animal == nullptr)
    {
        std::cout << "Pointer has been reset to null!\n";
    }
    else
    {
        std::cout << "This animal is a " << animal->GetGender() << " " << animal->GetType() << " named " << animal->GetName();
        std::cout << " and " << animal->GetPronoun() << " says ";
        animal->Speak();

        std::cout << animal->GetName() << " weighs " << animal->GetWeight() << " lbs and has " << animal->GetEyeColor() << " eyes.\n";
    }
}

int main()
{
    std::unique_ptr<Animal> wiskers{ std::make_unique<Cat>("Wiskers", "Male", "Cat", "He", 5, 9, Animal::EyeColor::Yellow ) };
    std::unique_ptr<Animal> fido{ std::make_unique<Dog>("Fido", "Female", "Dog", "She", 20, Animal::EyeColor::Blue ) };
    std::unique_ptr<Animal> bessie{ std::make_unique<Cow>("Bessie", "Female", "Cow", "She", 900, Animal::EyeColor::Brown ) };

    //wiskers.reset();

    CheckForNull(wiskers.get());
    wiskers->AdditionalInfo();
    wiskers->Fall();
    wiskers->Move();

    std::cout << '\n';

    CheckForNull(fido.get());
    fido->AdditionalInfo();
    fido->Fall();
    fido->Move();

    std::cout << '\n';

    CheckForNull(bessie.get());
    bessie->AdditionalInfo();
    bessie->Fall();
    bessie->Move();

    return 0;
}
Last edited on
If you're going to do much with inter-connected classes, I'd suggest you read up on UML and Patterns etc. The 2 books I'd suggest as a starter are:

'Applying UML and Patterns' by Craig Larman
https://www.amazon.co.uk/gp/product/0131489062

'Design Patterns' By Gamma, Helm, Johnson & Vlissides (often referred to the Gang Of Four book [GoF])
https://www.amazon.co.uk/gp/product/0201633612
(This is dated (1995), but I don't know of a better more up-to-date book on OOS design)
Pages: 12