Inheriting from a base class that inherits from another base class

Pages: 12
Im reading through C++ Primer 5th edition and I got to chapter 15 about object oriented programming and virtual, so i decided to try to do it myself, and I had an animal class that cat and tiger inherit from and that worked fine, but what if i make a base class specifically for cats? Cats are Felidae, and Felidae are Animalia, so I wanted to inherit from Animalia and have cat and tiger inherit from Felidae, but I dont know if what I did was correct, something doesnt seem right to me but I’m not quite sure what.

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

#include <iostream>
using namespace std;

class Animalia
{
    public:
        Animalia() = default;
        virtual void Speak()
        {
            cout << "Default speech" << endl;
        }
};

class Felidae : public Animalia
{
    public:
        Felidae() = default;
        virtual void Speak() = 0;
};

class Tiger : public Felidae
{
    public:
        Tiger() = default;
        void Speak() override
        {
            cout << "*Chuffing Noise*" << endl;
        }
};

class Cat : public Felidae
{
    public:
        Cat() = default;
        void Speak() override
        {
            cout << "Meow" << endl;
        }
};

int main() 
{
    Animalia* animal;
    Felidae* felidae;
    Tiger tiger;
    Cat cat;

    felidae = &tiger;
    felidae->Speak();

    felidae = &cat;
    felidae->Speak();

    //Do i use animal or felidae to access these?
    //animal = &tiger;
    //animal->Speak();

    //animal = &cat;
    //animal->Speak();

    return 0;
}
Hi,

I would have had:

1
2
3
4
5
Tiger* Mytiger;
MyTiger->Speak();

Cat* MyCat;
MyCat->Speak();


No need to mention Animalia or Felidae here.

The real power comes when one has a container of pointers to the base class, then loop through calling the function:

1
2
3
4
5
6
7
8
9
10
11
Tiger* Mytiger;
Cat* MyCat;

std::vector<Animalia*> AnimalCollection;

AnimalCollection.push_back(MyTiger);
AnimalCollection.push_back(MyCat);

for (const auto& TheAnimal : AnimalCollection) {
         TheAnimal->Speak();
}


It's a similar idea if one had a function SpeakAll:

1
2
3
4
5
void SpeakAll(const std::vector<Animalia*>& AnimalCollection) {
   for (const auto& TheAnimal : AnimalCollection) {
         TheAnimal->Speak();
   }
}


Now the function doesn't care about the types of all the individual animals: the compiler does the right thing. One could derive more animals, the function will still work.

This because the function expects an animal pointer, but we gave it a Tiger pointer instead: the compiler knows to execute Tiger->Speak() when the code says TheAnimal->Speak();

This is a very powerful feature.
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
#include <iostream>

class Animalia
{
protected: // USEFUL TOO!
    std::string speak{"Animalia speaking"};

public:
    Animalia(){};
    void Speak() { std::cout << speak << '\n';}
    void set_speak(std::string spk){speak = spk;}
};


class Felidae : public Animalia
{
private:
    std::string spk = "Felidae speaking";
public:
    Felidae(){ speak = spk; }
};


class Tiger : public Felidae
{
private:
    std::string spk = "Tiger Speaking";
public:
    Tiger(){speak = spk;}
};


class Cat : public Felidae
{
public:
    Cat(){speak = "Cat Speaking";} // AN ALTERNATIVE
};


int main()
{
    Animalia* animal = new Animalia; animal -> Speak();
    Felidae* felidae; // THIS IS AN UNINITIALIZED POINTER SO CAN'T Speak()

    Cat cat; cat.Speak();
    Tiger tiger; tiger.Speak();

    felidae = &tiger; felidae -> Speak();
    felidae = &cat; felidae -> Speak();

    animal = &tiger; animal->Speak();
    animal -> set_speak("Roar"); animal->Speak();

    animal = &cat; animal->Speak();
    cat.set_speak("Meow"); cat.Speak();

    animal = nullptr;
    delete animal;

    return 0;
}



Animalia speaking
Cat Speaking
Tiger Speaking
Tiger Speaking
Cat Speaking
Tiger Speaking
Roar
Cat Speaking
Meow
Program ended with exit code: 0
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
#include <iostream>
using namespace std;

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

class Felidae : public Animalia {
public:
	void Speak() const override { cout << "Default Felidea\n"; }
};

class Tiger : public Felidae {
public:
	void Speak() const override { cout << "*Chuffing Noise*\n"; }
};

class Cat : public Felidae {
public:
	void Speak() const override { cout << "Meow\n"; }
};

int main()
{
	const Animalia* const animals[] {new Animalia, new Felidae, new Tiger, new Cat};

	for (const auto& an : animals)
		an->Speak();

	for (auto& an : animals)
		delete an;
}



Default Animalia
Default Felidea
*Chuffing Noise*
Meow


or using std::unique_ptr

1
2
3
4
5
6
7
int main()
{
	const std::unique_ptr<Animalia> animals[4] {std::make_unique<Animalia>(), std::make_unique<Felidae>(), std::make_unique<Tiger>(), std::make_unique<Cat>()};

	for (const auto& an : animals)
		an->Speak();
}

Last edited on
@OP
The point about inheritance is that all Animalia, of any type according to your model are able to speak, it's only what they say that changes.
In other words, the function Speak() only needs to be written once, unless of course, there are cases down the heirarchy that need specific attention.
The ability to speak and also set what they say is inherited by all Animalia down the line.
@Chay

You are on the right track with having Animalia and Felidae classes as being abstract - it doesn't really makes sense to create an object with these. So what you have there is good.

There are several ways of using pointers: Raw pointers; new and delete ; and smart pointers such as std::unique_ptr and std::shared_ptr.

In the code below I have used a std::vector with raw pointers. This is fine for small programs like this, but it can be a problem when there is large amounts of data: one could get a stack overflow, because the stack memory is small. There are ways around that, but that is another story.

new and delete allocate memory on the heap, which is where we want it, but if something throws an exception, delete is never reached and memory is leaked.

The commented section shows using smart pointers. It might be harder to understand, but it is probably the best way to do things.

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

class Animalia
{
public:
    Animalia() = default;
    virtual void Speak()
    {
        std::cout << "Default speech\n";
    }
};

class Felidae : public Animalia
{
public:
    Felidae() = default;
    virtual void Speak() = 0;
};

class Tiger : public Felidae
{
public:
    Tiger() = default;
    void Speak() override
    {
        std::cout << "*Chuffing Noise*\n";
    }
};

class Cat : public Felidae
{
public:
    Cat() = default;
    void Speak() override
    {
        std::cout << "Meow\n";
    }
};

int main()
{
    Tiger MyTiger;
    Cat MyCat;

    std::vector<Animalia*> Animals {&MyTiger, &MyCat};
    
    // std::vector<std::unique_ptr<Animalia>> Animals;
    
    // Animals.emplace_back(std::make_unique<Tiger>());
    // Animals.emplace_back(std::make_unique<Cat>());

    for (auto&& an : Animals)
    {
        an->Speak();
    }

    return 0;
}


*Chuffing Noise*
Meow


In the for loop, the auto&& is a forwarding reference - it works with lvalues, rvalues, const and non-const.

Anyway, the main take away points are:

Containers and functions can be defined to take a pointer to the base class, but pointers to derived class can passed to them.

This is very powerful, we can add any kind of Animal into our vector, the for loop does not need to change.

Functions can be inherited, or overridden. Inheriting is good, functions can be written once. Overriding is used when one wants different behaviour.
Last edited on
Thank you for all the responses. Ive seen examples showing a pointer to the base class and then having that assigned to the derived class object like i have it in my OP, what is the reason for that if it’s not necessary? Just for illustration purposes?

I believe I understand now why virtual is used and when, but when I have multiple base classes, like in my OP where the derived class is a base, so Felidae is derived from Animalia because its a class of large cat, and cats and tigers are Felidae, so even though there is a Root Base class(not sure of the correct term) and a derived class thats also a base, then the way I currently have it is the correct way to design this? I wasnt sure if Felidae class was necessary or not but since classes and inheritance are to model real life i figured that it should be in that heirarchy.

I would go with smart pointers as well, I’ll have to go back and read up on them though. I’m mainly just reading the book through and marking things that I dont quit grasp then go back and take notes and re read to get a better grip on it.
For polymorphism, you need to use pointers (or references). You assign a pointer to a derived class to a type of the pointer to base class. You use pointer to base and the correct class member function is used as appropriate. This is polymorphism - defined by using virtual with the base class member function (there's no need to use virtual with derived classes).
Ok thank you. Now with the base class thing, if I have multiple base classes, but not using multiple inheritance, just single inheritance down a chain, would Cat and tiger be assigned to Animal class pointer or Felidae Class pointer? Animalia is the master base class, but Felidae is the base class for all Cat types like House cats, Tigers, Lions etc, and for example, if I added a class for horses, Horses would have an Equidae class and the types of horses would inherit from that?

My guess is all things common to all animals that exist would be in Animalia class, like movement, Eating, Drinking, Sleeping etc, and the stuff common to say cats like Cleaning Fur, Meowing, etc would be in Felidae Class.

Basically I am just wondering if I have multiple base classes like that, do I assign then to the pointer of the child base class(Felidae, Equidae) or the master base class(Animalia).
Last edited on
Now with the base class thing, if I have multiple base classes, but not using multiple inheritance, just single inheritance down a chain, would Cat and tiger be assigned to Animal class pointer or Felidae Class pointer? Animalia is the master base class, but Felidae is the base class for all Cat types like House cats, Tigers, Lions etc, and for example, if I added a class for horses, Horses would have an Equidae class and the types of horses would inherit from that?

That really depends on your code, and what it does. If it's useful to have multiple layers of hierarchy in your classes, then sure. But there's no point having useless layers of class inheritance just because you happened to think of lots of categories and sub-categories.

Does your code need to model properties or behaviour that are common to all Felidae, but not to all Animalia? Then, sure, have that layer in there, if it will usefully avoid code duplication in multiple cat-like classes. If not, don't clutter your code with it.

As a general rule, these days the consensus is that over-complicated class hierarchies often lead to code that's difficult to understand and maintain, so be judicious when adding layers to your hierarchy.
Ok, yeah I wouldnt make any of the classes if they wouldnt be used, but I think in this specific case with these classes it would be fine, all animals can eat and sleep, but not all can gallop, or meow, so I would create a Felidae pointer and assign cat and lion to it and then an Equidae class pointer and have horses and ponies assigned to that correct? Since they all already inherit the eat, sleep, move, speak functions from Animalia, the specific things to that family of animal would then be inherited on that animal from its family class(Felidae and Equidae). I believe I understand now.
Any number of classification diagrams exist on the web and they might give you more inspiration how to build up a useful/accurate/meaningful hierarchy and be able to differentiate a lot more easily how/why a particular animal sets itself apart from others. You'll also be able to stay with what you have.

When you think about it what noises animals make leaves you with dreaming up all the noise words like, hiss, roar, crow, woof, growl, neigh, croak, caw, blah blah blah which becomes fairly tedious after a while and nowhere near as clear cut as a bool spine_yes_no attribute, as the real classification does.

One such diagram is https://www.pinterest.com.au/pin/365213851014333461/

@Chay

Here is another resource on YouTube: thechernoproject

https://www.youtube.com/user/TheChernoProject

He has all kinds of topics with approx 15 min video for each.

Hopefully you find this helpful :+)
MikeyBoy wrote:
That really depends on your code, and what it does. If it's useful to have multiple layers of hierarchy in your classes, then sure. But there's no point having useless layers of class inheritance just because you happened to think of lots of categories and sub-categories.

Does your code need to model properties or behaviour that are common to all Felidae, but not to all Animalia? Then, sure, have that layer in there, if it will usefully avoid code duplication in multiple cat-like classes. If not, don't clutter your code with it.

As a general rule, these days the consensus is that over-complicated class hierarchies often lead to code that's difficult to understand and maintain, so be judicious when adding layers to your hierarchy.


Ch1156 wrote:
Ok, yeah I wouldnt make any of the classes if they wouldnt be used, but I think in this specific case with these classes it would be fine, all animals can eat and sleep, but not all can gallop, or meow, so I would create a Felidae pointer and assign cat and lion to it and then an Equidae class pointer and have horses and ponies assigned to that correct? Since they all already inherit the eat, sleep, move, speak functions from Animalia, the specific things to that family of animal would then be inherited on that animal from its family class(Felidae and Equidae). I believe I understand now.


I agree with MikeyBoy that for this particular project a class hierarchy that has all the levels of Taxonomy would be useful, because each level can hold information that can be inherited to a lower level. Having said that, one should only create classes for the type of things one needs. For example one may not need every type of cat, and if not interested in snails, then don't create them. I would have all 10 levels of Taxonomy for whatever one did create though, that way one can print out that detail for whatever animal is created. Also remember the compiler will throw away any classes that are not needed, so If one had 20 classes of types of cat, and 25 classes of horse, but in main only created 2 cats and 2 horses, the linker will throw away the rest. If the 2 cats are closely related, there may be 11 classes to cover both of them. To make a horse, start at the Order level, so only 6 classes for that, 1 more for a different horse.

In terms of a large class hierarchy be difficult to understand & maintain, I would assert that the scientific classification is very well defined. The benefits of virtual polymorphism and inheritance allow new organisms to be added easily, but the biggest problem is having to write code for every specialisation. I see this as being a problem, but not an insurmountable one. Outputting data would vastly improved with the use of reflection. For example Oviparity (lays eggs) is a property that I am not sure is part of the scientific classification, so that data would have to coded (bool oviparity ) hopefully fairly high in the class hierarchy, but in a number of different places. Reflection would help so that one could output all the variables in a class without having to explicitly specify them.

Ch1156 wrote:
.... so I would create a Felidae pointer and assign cat and lion to it and then an Equidae class pointer and have horses and ponies assigned to that correct?


No, I would create classes Cat and Lion that derive from Felidae; and create classes Horse and Pony that derive from Equidae; then put pointers to Cat, Lion, Horse and Pony into your Container of pointers to Animalia.

The following is an example where data is stored in a base class, then inherited for use in a derived class:

Felidae could have a protected variable bool CanPurr = false; classes such as Cheetah and HouseCat (whatever their scientific classifications are) would set that value to true. Classes such as Lion would leave it as false.

I have a few things on today, but I will try to produce some code to show what I mean. :+)
Last edited on
Now with the base class thing, if I have multiple base classes, but not using multiple inheritance, just single inheritance down a chain, would Cat and tiger be assigned to Animal class pointer or Felidae Class pointer?

Tiger IS-A Felidae IS-A Animal. Therefore, Tiger IS-A Animal.

Some pointer conversions are implicit.
1
2
3
4
5
Tiger tigger;
Tiger* pt = &tigger; // trivial, isn't it?
Felidae* pf = pt; // ok, implicit
Animal* pa = pf; // ok, implicit
pa = pt; // ok, implicit 

You can use interface of Animal via 'pa'.
You can use interface of Felidae (which includes interface of Animal) via 'pf'.
You can use interface of Tiger (which includes interface of Felidae ...) via 'pt' and 'tigger'.

What if you have a 'pa', but don't know what it really points to? (This is a runtime problem.)
1
2
3
4
5
6
Tiger* t = dynamic_cast<Tiger*>( pa );
// t is valid, if pa points to Tiger. Else t == nullptr
Horse* h = dynamic_cast<Horse*>( pa );
// h is valid, if pa points to Horse. Else h == nullptr
Felidae* f = dynamic_cast<Horse*>( pa );
// f is valid, if pa points to any Felidae (e.g. Tiger or Cat). Else f == nullptr 

You do need the dynamic_cast only if you need to use the interface that only the derived class has (but base class does not).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Animalia
{
public:
    Animalia() = default;
    void Speak() const;
private:
    virtual void speak_impl() const = 0; // pure virtual
};

void Animalia::Speak() const {
  speak_impl();
  cout << endl;
}


class Tiger : public Felidae
{
public:
    Tiger() = default;
private:
    virtual void speak_impl() const override { cout << "Ha!" };
};

Here the Animalia is abstract. You can't create concrete object Animalia ufo;

All Animalia have Speak() in their interface. Some bits of speech they all do.
Derived classes can/have to override part of the speech.
So I have the next few days off of work and I want to try to get this virtual thing straightened out in my head. So i’ll try to list what I understand and don’t understand and how I believe things work and are used and if i’m wrong then please correct me.

I’ve read all the replies here and i’ve taken that into account and explained below my reasoning for things and what I know and whats confusing me.

So With inheritance I pretty much get all of that. Pretty simple concept. I have some code that I want another class to reuse, but don’t want to re-type it all since it would be a waste of time and resources. So if I have a game, which is something that i will be making once I learn the more advanced stuff here, lets say I want to make NPC’s.

There is probably a few different ways I can go about this, but what I would do is this: Make a Character class and all things common to all Non-playable characters will go in there. And if there are any NPC’s that require special functionality that only that character will have, then make a class for them that inherits from Character.

So single or chain inheritance as I like to call it, is simple to understand mechanically, but conceptually I need to learn more so I can properly structure and build classes, cause sometimes I have a hard time knowing what things should be a class and what should already go in an existing class. But that usually only happens with things that are kind of vague.

Now as for Virtual, same thing, conceptually I believe I understand it, but it’s a little vague. So virtual combined with inheritance allow for runtime polymorphism, as opposed to compile time polymorphism.

Compile time polymorphism is something like function or operator overloading, whereas runtime polymorphism is function overriding, where a derived class overrides the base classes definition of it, so the base classes functions can be generic and then can be overridden by the derived classes using a pointer to access the Vtable.

One thing i’m having trouble with is determining how to access what I need, I see on multiple websites and also in my books as well that they usually access the derived class through a base class pointer like so:

1
2
3
4
5
Base* base;
Derived derived;
base = &derived;

Base->Print();


I would personally do:

1
2
unique_ptr<Felidae> felidae = make_unique<Tiger>();
felidae->Speak();


I don’t like to use raw pointers, they have their place but I like using smart pointers whenever possible since they clean themselves up and are much safer to use.

So when I have multiple classes like this, we’ll use the Animalia class hierarchy for example, where there are multiple base classes in a single chain(not using multiple inheritance) then I would assume you inherit from the base class that other classes directly derive from it like Cat and Tiger Derive from Felidae, since Felidae inherits from Animalia, it makes no sense to use a pointer to Animalia since we need the functions in Felidae and Felidae already inherits from Animalia so we have access to those functions anyways. The only reason I can see to make a pointer to base class is to use it to make a generic animal. Unless it has pure virtual function in it then it cant be instantiated in main.

I could put them in a container of pointers to Animalia but what if Animalia class is an interface class?

Thats my rational for doing it that way.

So virtual makes sense to me but when I deviate from whats shown to me in examples i seem to get lost. Like most examples have a base class and one or two derived and they show two examples using compile time polymorphism and run time polymorphism an that makes total sense. But when I add a sub base class, like Felidae and Equidae do I make those function in there virtual or are they already overwriting whats in Animalia?

My assumption is that I should not make virtual functions defined in Animalia virtual again in the two sub base classes but if I add something new to either Felidae or Equidae that is unique to al Felidae and Equidae then those should be virtual, correct?

Hopefully this made some kind of sense, I may have left some things out but it’s been a long night and this is getting pretty long so i’ll leave it here, I just wanted to explain what I already know and how I would do something so you guys know where I’m at with this.
Last edited on
Ok so I finished an example and made an aircraft hierarchy. I left stuff out for the sake of brevity, I'm not making an actual program, this is just to get the hang of using virtual and structuring my classes so things arent fully fleshed out in the program but the way everything is structured and named is how I would actually do it.

just looking for feedback, I tried to incorporate the suggestions posted in this thread.

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

using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::unique_ptr;
using std::make_unique;


class Aircraft
{
    public:
        Aircraft() = default;

        string GetName() const { return mName; }
        string GetOrigin() const  { return mOrigin; }

        float GetMaxSpeed() const  { return mMaxSpeed; }
        double GetUnitCost() const  { return mUnitCost;}
        float GetWeight() const  { return mWeight; }

        int GetYearProduced() const  { return mYearProduced; }
        int GetNumberBuilt() const  { return mNumberBuilt; }

        virtual void SetSprite() = 0;

        //We dont want the user making a generic aircraft, only making a specific one for this project.
    protected:
        Aircraft
        (
            const string& name, 
            const string& origin, 
            float maxSpeed, 
            double unitCost, 
            float weight, 
            int yearProduced,
            int numberBuilt
        ) : mName(name), 
            mOrigin(origin), 
            mMaxSpeed(maxSpeed), 
            mUnitCost(unitCost), 
            mWeight(weight), 
            mYearProduced(yearProduced),
            mNumberBuilt(numberBuilt)
        {}

    private:
        string mName{};
        string mOrigin{}; //Country of origin
        float mMaxSpeed{};
        double mUnitCost{};
        float mWeight{};
        int mYearProduced{};
        int mNumberBuilt{};
};

class Fighter : public Aircraft
{
    public:
        Fighter() = default;

		void SetSprite() override
        {
            cout << "Fighter Aircraft Sprite Set" << endl;
        }

        string GetPrimaryWeaponName() const { return mPrimaryWeaponName; }
        string GetSecondaryWeaponName() const { return mSecondaryWeaponName; }
        int GetPayload() const { return mPayload; }

        //Maybe could make a bool that says hasCountermeasure? Not all aircraft have countermeasures so this
        //Shoudnt go in aircraft but military planes have it so a militaryAircraft class could probably
        //ber made and put in there along with other things specific to military aircraft.
        void DeployChaffCountermeasure()
        {
            cout << "Chaff Countermeasure deployed" << endl;
        }

        virtual void Eject() const = 0;
        virtual void FirePrimaryWeapon() const = 0;
        virtual void FireSecondaryWeapon() const = 0;
        
    protected:
        Fighter
		(
            const string& name, 
            const string& origin, 
            float maxSpeed, 
            double unitCost, 
            float weight, 
            int yearProduced,
            int numberBuilt,
            const string& primaryWeaponName,
            const string& secondaryWeaponName,
            int payload
        ) : Aircraft{ name, origin, maxSpeed, unitCost, weight, yearProduced, numberBuilt }, mPrimaryWeaponName(primaryWeaponName), 
                                                                                             mSecondaryWeaponName(secondaryWeaponName),
                                                                                             mPayload(payload)
        {}

    private:
        string mPrimaryWeaponName{};
        string mSecondaryWeaponName{};
        int mPayload{};
};

class F35Lightening2 : public Fighter
{
    public:
        F35Lightening2() = default;
        F35Lightening2
		(
            const string& name, 
            const string& origin, 
            float maxSpeed, 
            double unitCost, 
            float weight, 
            int yearProduced,
            int numberBuilt,
            const string& primaryWeaponName,
            const string& secondaryWeaponName,
            int payload
        ) : Fighter{ name, origin, maxSpeed, unitCost, weight, yearProduced, numberBuilt, primaryWeaponName, secondaryWeaponName, payload }
        {}

        void SetSprite() override
        {
            cout << "F-35 Lightening II sprite set" << endl;
        }

        void Eject() const override
        {
            cout << "Ejected out of canopy" << endl;
        }

        void FirePrimaryWeapon() const override
        {
            cout << "Firing 25mm GAU-22 cannons" << endl;
        }

        void FireSecondaryWeapon() const override
        {
            cout << "Firing Air-to-air missiles" << endl;
        }
};

class Cargo : public Aircraft
{
    public:
        Cargo() = default;

        float GetMaxTakeoffWeight() const { return mMaxTakeoffWeight; }

        virtual void LoadCargo() = 0;

    protected:
        Cargo
		(
            const string& name, 
            const string& origin, 
            float maxSpeed, 
            double unitCost, 
            float weight, 
            int yearProduced,
            int numberBuilt,
            float maxTakeoffWeight
        ) : Aircraft{ name, origin, maxSpeed, unitCost, weight, yearProduced, numberBuilt }, mMaxTakeoffWeight(maxTakeoffWeight)
        {}

    private:
        float mMaxTakeoffWeight{};
};

class C17Globemaster : public Cargo
{
    public:
        C17Globemaster() = default;
        C17Globemaster
		(
            const string& name, 
            const string& origin, 
            float maxSpeed, 
            double unitCost, 
            float weight, 
            int yearProduced,
            int numberBuilt,
            float maxTakeoffWeight
        ) : Cargo{ name, origin, maxSpeed, unitCost, weight, yearProduced, numberBuilt, maxTakeoffWeight}
        {}

		void SetSprite() override
        {
            cout << "C-17 Globemaster III Aircraft Sprite Set" << endl;
        }

        void LoadCargo() override
        {
            cout << "Loading cargo through back ramp" << endl;
        }
};


int main()
{
    unique_ptr<Fighter> fighter = make_unique<F35Lightening2>("F-35 Lightening II", "United States", 1200, 94000000.000, 29300.00, 2006, 625, "25mm Canon", "Air-to-air Missiles", 2000);

    cout << "Name: " << fighter->GetName() << endl;
    cout << "Country of origin: " << fighter->GetOrigin() << endl;
    cout << "Max Speed: " << fighter->GetMaxSpeed() << endl;
    cout << std::fixed <<  "Unit cost: $" << fighter->GetUnitCost() << endl;
    cout << "Weight: " << fighter->GetWeight() << endl;
    cout << "Year Produced: " << fighter->GetYearProduced() << endl;
    cout << "Number Built: " << fighter->GetNumberBuilt() << endl;
    cout << "Primary Weapon: " << fighter->GetPrimaryWeaponName() << endl;
    cout << "Number Built: " << fighter->GetSecondaryWeaponName() << endl;
    cout << "Payload: " << fighter->GetPayload() << endl;

    cout << "\n\n";

    fighter->SetSprite();
    fighter->FirePrimaryWeapon();
    fighter->FireSecondaryWeapon();
    fighter->DeployChaffCountermeasure();
    fighter->Eject();


    unique_ptr<Cargo> cargo = make_unique<C17Globemaster>("C-17 Globemaster", "United States", 590, 200000000, 282400, 1980, 279, 585000);

    cout << "\n------------------------\n" << endl;

    cout << "Name: " << cargo->GetName() << endl;
    cout << "Country of origin: " << cargo->GetOrigin() << endl;
    cout << "Max Speed: " << cargo->GetMaxSpeed() << endl;
    cout << "Unit cost: $" << cargo->GetUnitCost() << endl;
    cout << "Weight: " << cargo->GetWeight() << endl;
    cout << "Year Produced: " << cargo->GetYearProduced() << endl;
    cout << "Number Built: " << cargo->GetNumberBuilt() << endl;
    cout << "Max Takeoff Weight: " << cargo->GetMaxTakeoffWeight() << endl;

	cout << "\n\n";

    cargo->SetSprite();
    cargo->LoadCargo();

    return 0;
}
My post was too long to add this but I was also going to say this:

I probably could have printed everything with a container that holds a pointer to Aircraft and printed all the info for Fighter and Cargo that way but in a real program like a game there would be few situations where I would want to print all information at once so I just went the route of outputting each function manually.

I believe I understand how to use virtual and pure virtual, its the structuring of things thats an area I need to work on, but Im getting much better at that as well I think.
Last edited on
Your extract produces some output but crashes.
It would be a good idea if you posted code that you have checked and runs.


I compiled and ran it n VS 2019 with the latest update, it ran for me with no issue or crashing. Also ran it in CPP shell and another compiler on my ipad and they all work fine.
Last edited on
Pages: 12