Best way to modify a variable in a class

Pages: 1234
Standard library containers (and utilities for smart pointers) do memory allocation on our behalf, so not for "any kind of".

jonnin wrote:
pointers can acquire new blocks of memory (that represent an object instance or variable).
a pointer also will allocate
A pointer must be resized manually

I would not say it like that. A pointer does exactly one thing: holds an address. It does not "get", "allocate", nor "resize".

We can allocate memory dynamically for objects. (And not really "we", but something something.)
Unlike variables, such allocated block of memory has no name that we could use; all we get from allocation is an address.
Address is a value. Value that we (must) store somewhere. In pointer variable.

you have to allocate new space of the new size, copy the old data to the new location, and surrender the old data's memory
... its a great place to mess up an break your program badly

This is spot on. The "you/we" has to write the code.
When we use vector, we use code that is already written (and probably rather good quality).

a vector will return any memory it used to the operating system when it goes out of scope or under various other conditions.

Yes. A vector is a custom type with destructor that deallocates the dynamic memory block that the vector "owns" on the "death of vector".
Similarly, smart pointers are custom types with destructor that deallocates the dynamic memory block that the smart pointer object "owns".

A raw pointer is as simple type as an int; it does nothing when its life ends.
That is fair. I didn't say it well but yes, the coder is writing lots of high risk, hands-on code when using pointers (even smart ones), code that probably could and should have been avoided.

------------------
when to use pointers? When you get one from a library, or need to send one to a library (a lot of cross-language tools use C strings for example). When you interface to C code. When you deal with some types of polymorphism. When you want to avoid copying fat objects (like your attacks, if you had a fixed list of 20 used by 500 characters). Just a few of the big ones...

but the problem is the question. Pointers can do a bunch of different things, and its important whether you are talking about dynamic memory vs polymorphism vs reference-like uses vs C code and so on.

The question even transcends pointers. Its like asking 'how would you know about vectors' if you were taught to use arrays in an old C++ book or from someone with an outdated skill set. Its like that... for most people, most of the time, the way you learn there is a better way is either by doing it wrong and getting peer reviewed or by stumbling across it in someone else's code or reading up.
C++ is a constantly updating language (every 3 years) and hence requires effort (quite a bit) to learn and to keep up-to-date. No book is going to cover the whole language and how to use it 'properly'. I came to C++ via Assembler (PDP-11), Pascal and then c. I was taught the Pascal language in a few weeks (it's really a quite simple language). The rest of the course was how to use the language (eg lists, data processing etc).

In C++ there is no problem using a non-owning raw pointer as required. The issue is with owning-pointers. This is where you use managed pointers so that memory is automatically released as needed.

But the question as to when to use a pointer at all (including managed) really comes from experience, reading blogs/articles/books etc and knowing the alternative options and their pros/cons. Note that not everything you read on the internet or older books may be good practice now for the latest C++ versions. There's load of C++ available that comes from C++ 98 or even earlier. You really need knowledge to know which is 'good' and which is 'bad.
I appreciate the responses, as with most stuff I have learned throughout the years, coming into contact with actual scenarios where the feature is genuinely needed helps me learn a ton, so during my game programming I'm sure i may encounter cases where i may need to use pointers.
I did have a question about polymorphism since we mentioned it earlier. If we have a polymorphic class, say Animal, and we have a Cat child class, what happens if we have a child class that has unique methods that only apply to that child class? downcasting is looked down upon so what do we do? I understand that if we have a polymorphic class we can just have a generic function like Speak() or Walk and make those virtual, then we can create unique definitions for them in the child classes that inherit it, but what happens when the child class has methods that do not fit for any other animal type? would you still just put it in the Animal base class?
When would you call those functions?
Well if one class, say the cat class has a function that is only specific to Cats, then anytime its necessary to call it. Perhaps i misunderstand the point of polymorphism though. If the goal of polymorphism is just simply to create in interface of functions that will be overwritten by the child classes then i can understand that pretty simply, but sometimes with inheritance you have classes that perform actions that are only specific to that class, so im unsure how to navigate that.
If you want to call a function via polymorphism then the base class has to have a definition of that function (unless you use a pure virtual class [abstract] where the base has no definition [base definition is set to = 0] but each derived class has to). A derived class can have their own specific functions but these can't be called via polymorphism but only via the class direct. Consider:

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>

struct Base {
        virtual ~Base() {}
	virtual void hello() const {
		std::cout << "Hello from Base\n";
	}
};

struct Derv : public Base {
	void hello() const override {
		std::cout << "Hello from Derv\n";
	}

	void bye() const {
		std::cout << "Bye from Derv\n";
	}
};

void proc(const Base& cls) {
	cls.hello();
	//cls.bye();	// NO. Base doesn't have a member bye()
}

int main() {
	Base b;
	Derv c;

	proc(b);
	proc(c);

	c.bye();         // OK. using Derv class direct
}


displays:

1
2
3
Hello from Base
Hello from Derv
Bye from Derv


Last edited on
Lets make the "proc" a bit different:
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
#include <iostream>

struct Visitor;

struct Base {
	virtual void hello() const {
		std::cout << "Hello from Base\n";
	}
	virtual void apply( const Visitor& v ) const;
};

struct Derv : public Base {
	void hello() const {
		std::cout << "Hello from Derv\n";
	}

	void bye() const {
		std::cout << "Bye from Derv\n";
	}
	void apply( const Visitor& v ) const;
};

struct Visitor {
    void visit( const Base* b ) const {
        b->hello();
    }
    void visit( const Derv* b ) const {
        b->hello();
        b->bye();
    }
};

void Base::apply( const Visitor& v ) const {
    v.visit( this );
}

void Derv::apply( const Visitor& v ) const {
    v.visit( this );
}

int main() {
	Base b;
	Derv c;
	Visitor e;
	b.apply( e );
	c.apply( e );
	std::cout << "===\n";
	Base& p = c;
	p.apply( e );
}

Prints:
Hello from Base
Hello from Derv
Bye from Derv
===
Hello from Derv
Bye from Derv


Did I "downcast"?
struct Vistor visit() function is overloaded so which function version is used depends upon the type of the argument - and hence what the function actually does.
So im a bit confused, so in your examples, I don't need to downcast in order to access a child classes non virtual method? I asked AI for assistance with a downcasting function as my knowledge of pointers and templates is pretty weak at the moment but I understand whats happening here mostly.

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

class Animal
{
    public:
        Animal(const std::string& name, int age) : mName(name), mAge(age)
        {}

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

        int GetAge() const
        {
            return mAge;
        }

        virtual void Speak() = 0;
        virtual void Move() = 0;
        virtual void Sleep() = 0;

    private:
        std::string mName{ "" };
        int mAge{ 0 };

};

class Cat : public Animal
{
    public:
        Cat(const std::string& name, int age) : Animal{name, age}
        {}

        Cat() = default;

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

        void Move() override
        {
            std::cout << "A cat moves like this\n";
        }

        void Sleep() override
        {
            std::cout << "A cat sleeps like this\n";
        }
        
        void SomethingUniqueToCats()
        {
            std::cout << this->GetName() << " Does something Unique\n";
        }

    private:
};

template<typename Derived, typename Base>
std::unique_ptr<Derived> Downcast(std::unique_ptr<Base>& basePtr) 
{
    if (Derived* result = dynamic_cast<Derived*>(basePtr.get())) 
    {
        return std::unique_ptr<Derived>(static_cast<Derived*>(basePtr.release()));
    }
    else 
    {
        return nullptr;
    }
}

int main()
{
    std::unique_ptr<Animal> cat = std::make_unique<Cat>("Wiskers", 2);

    std::cout << cat->GetName() << " is " << cat->GetAge() << " years old, and it says ";
    cat->Speak();

    Downcast<Cat>(cat).get()->SomethingUniqueToCats();
}
Note that you don't need constructor for Cat if you use a using statement. If you're using virtual then you should have a virtual destructor in the base class.

Also why unique_ptr in Downcast? The only place you should be using unique_ptr is in main so that cat is a memory owner. No other code requires a memory owning pointer. Also, you need to check the return from Downcast as it may be nullptr and you can't de-reference a nullptr. Note that dynamic_cast returns a nullptr if it can't cast so there's no need to check in Downcast.

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

class Animal
{
public:
	Animal(const std::string& name, int age) : mName(name), mAge(age) {}
	virtual ~Animal() {}

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

	int GetAge() const {
		return mAge;
	}

	virtual void Speak() const = 0;
	virtual void Move() const = 0;
	virtual void Sleep() const = 0;

private:
	std::string mName;
	int mAge { };

};

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

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

	void Move() const override {
		std::cout << "A cat moves like this\n";
	}

	void Sleep() const override {
		std::cout << "A cat sleeps like this\n";
	}

	void SomethingUniqueToCats() const {
		std::cout << GetName() << " Does something Unique\n";
	}
};

template<typename Derived, typename Base>
Derived* Downcast(Base* basePtr) {
	return dynamic_cast<Derived*>(basePtr);
}

int main() {
	const std::unique_ptr<Animal> cat { std::make_unique<Cat>("Whiskers", 2) };

	std::cout << cat->GetName() << " is " << cat->GetAge() << " years old, and it says ";
	cat->Speak();

	if (const auto ptr { Downcast<Cat, Animal>(cat.get()) }; ptr != nullptr)
		ptr->SomethingUniqueToCats();
	else
		std::cout << "Can't process unique!\n";
}

Last edited on
Well if one class, say the cat class has a function that is only specific to Cats, then anytime its necessary to call it.

Be more concrete, please.

Lets say you have a container of animals called 'farm'.
Lets also have functions that takes parameters and returns value.
You might have virtuals in the base to allow:
1
2
3
4
5
6
7
for ( auto animal : farm ) {
  auto cresult = animal->SomethingUniqueToCats(x, y, z);
  // use cresult
  auto dresult = animal->SomethingUniqueToDogs(w, h);
  // use dresult
  // ...
}

Or downcast to reach non-virtual members:
1
2
3
4
5
6
7
8
9
10
11
for ( auto animal : farm ) {
  if ( auto cat = std::dynamic_cast<Cat*>(animal) ) {
    auto result = cat->SomethingUniqueToCats(x, y, z);
    // use result
  }
  else if ( auto dog = std::dynamic_cast<Dog*>(animal) ) {
    auto result = dog->SomethingUniqueToDogs(w, h);
    // use result
  }
  // ...
}

That is fine and dandy.

Lets also assume that most of the time the farm has neither cats nor dogs. Presumably.
Will your "unique members" be such that they really have to be called "all the time"?

What if they are used only when you add animal to the farm? When you do know that you have a dog at hand -- somebody breeds dogs and knows that they are dogs.
If you are new to this stuff, you may be off in the weeds too soon.
I recommend that you find something really simple to start out, rather than special cases & oddballs, and circle back to the weird stuff later. Not that its not great examples and useful snippets, but just the basic concept and use cases are quite a bit to work through before going off on the sidebars.

Depending on who you ask, other things are PM too. Some may say that a union, for example, is a type of PM behavior. This matters because you may have some sources that you study treating PM as a concept and abstract way of doing things and others concrete to C++ OOP only.
Last edited on
Hmm, well I think I will just focus on learning polymorphism for right now, I understand polymorphism to a large degree, theres just some things i need to understand yet. I guess the only question I would have is how common is it to downcast? should i even worry about learning how to do it? or is it such a rare case its not even worth it? If I had a situation where I had a child class that did have a unique function that only that child class used, then I could just create a SomethingUnique() function in the base class and override it in the Cat class right? I would just not make it pure virtual so that way any classes that had unique functionality could implement it and the ones that didnt, dont have to.

Id just like some advice on how i should design my classes that have virtual function and any scenarios where the child class has unique functionality.

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

class Animal
{
    public:
        Animal(const std::string& name, int age) : mName(name), mAge(age)
        {}

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

        int GetAge() const
        {
            return mAge;
        }

        virtual void Speak() = 0;
        virtual void Move() = 0;
        virtual void Sleep() = 0;

        virtual void SomethingUnique()
        {
            std::cout << "Something unique\n";
        }

    private:
        std::string mName{ "" };
        int mAge{ 0 };

};

class Cat : public Animal
{
    public:
        Cat(const std::string& name, int age) : Animal{name, age}
        {}

        Cat() = default;

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

        void Move() override
        {
            std::cout << "A cat moves like this\n";
        }

        void Sleep() override
        {
            std::cout << "A cat sleeps like this\n";
        }
        
        void SomethingUnique()
        {
            std::cout << this->GetName() << " Does something Unique\n";
        }

    private:
};

int main()
{
    std::unique_ptr<Animal> cat = std::make_unique<Cat>("Wiskers", 2);

    std::cout << cat->GetName() << " is " << cat->GetAge() << " years old, and it says ";
    cat->Speak();
    cat->SomethingUnique();
}
Last edited on
simple to start out, rather than special cases & oddballs


I agree with that. I'd suggest getting 'a feel' for the C++ language broadly (width) and then start to delve deeper (depth) into the parts of the language.

There are numerous books devoted to OOP design (language agnostic) using UML etc and several on OOP design with C++ using design patterns etc. But to get to grips with these you should first have a broad understanding of C++.

NB Note my comment above re derived class constructor(s). Also you don't need to initialise an empty std::string to "" as the default constructor does this for you and the default {} value for an int/double is 0. so just need int mage{};

if you have multiple derived classes then possibly:

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

class Animal
{
public:
	Animal(const std::string& name, int age) : mName(name), mAge(age) {}
	virtual ~Animal() {}

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

	int GetAge() const {
		return mAge;
	}

	virtual void Speak() const = 0;
	virtual void Move() const = 0;
	virtual void Sleep() const = 0;

private:
	std::string mName;
	int mAge { };

};

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

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

	void Move() const override {
		std::cout << "A cat moves like this\n";
	}

	void Sleep() const override {
		std::cout << "A cat sleeps like this\n";
	}

	void SomethingUniqueToCats() const {
		std::cout << GetName() << " Cat does something Unique\n";
	}
};

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

public:
	void Speak() const override {
		std::cout << "Bow wow!\n";
	}

	void Move() const override {
		std::cout << "A dog moves like this\n";
	}

	void Sleep() const override {
		std::cout << "A dog sleeps like this\n";
	}

	void SomethingUniqueToDogs() const {
		std::cout << GetName() << " Dog does something Unique\n";
	}
};

void process(Animal* anim) {
	std::cout << anim->GetName() << " is " << anim->GetAge() << " years old, and it says ";
	anim->Speak();

	if (const Cat* typc { dynamic_cast<Cat*>(anim) }; typc)
		typc->SomethingUniqueToCats();
	else if (const Dog* typd { dynamic_cast<Dog*>(anim) }; typd)
		typd->SomethingUniqueToDogs();
}

int main() {
	const std::unique_ptr<Animal> cat { std::make_unique<Cat>("Whiskers", 2) };
	const std::unique_ptr<Animal> dog { std::make_unique<Dog>("Fido", 3) };

	process(cat.get());
	process(dog.get());
}


However if you have operations unique to particular derived classes then this might indicate an issue with the class design. Note that a derived class can be a base class for further derived classes. Also a class can be derived from multiple base classes.

PS. You might think that function process() should be part of the base class. However dynamic_cast requires a complete defined class. To have function process() as part of base you'd need to forward declare the defined classes and a forward declared class isn't a complete class so can't be used with dynamic_cast. Aaaahhhh...
Last edited on
seeplus mentioned earlier Learn C++ tutorials about pointers. https://www.cplusplus.com/forum/beginner/285818/#msg1242909

Learn C++ also is good for learning much of the ins and outs about classes. From the basics of stand-alone classes to inheritance and polymorphism. Spread across multiple chapters.

@Ch1156, even if you've dealt with classes before skimming the lessons would be a good refresher for what needs to be done.
Wait so i think i understand, so if im using polymorphism then generally most of the time i will not need to downcast, so if I have unique functionality for a derived class, i can make it so it can be called in the virtual function im supposed to override, and then that functionality can be used that way instead of downcasting to call it. So basically I'm just making interfaces for each derived class that I can use to create specific code tailored to that class, and if I need certain functionality like enemy specific code I can write it and call it in the virtual functions. Is my understanding correct? Can someone tell me if I'm right or wrong? This code isnt fully done but its just a fleshed out example, im not making anything with it, but something 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
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
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <format>
#include <algorithm>

enum class HealingPower
{
    Weak,
    Strong,
    Maximum
};

class Attack
{
    public:
        Attack(const std::string& name, int power, int level) : mName(name), mPower(power), mLevel(level)
        {}

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

        int GetPower() const
        {
            return mPower;
        }

        int GetLevel() const
        {
            return mLevel;
        }

        void IncreaseLevel()
        {
            mLevel++;
        }

    private:
        std::string mName{};
        int mPower{};
        int mLevel{};
};

class Character
{
    public:
        Character(const std::string& name, int health) : mName(name), mHealth(health)
        {}

        virtual void PerformAttack() = 0;
        virtual void Heal(HealingPower healingPower) final
        {
            switch (healingPower)
            {
                case HealingPower::Weak:
                    AddHealth(25);
                    break;

                case HealingPower::Strong:
                    AddHealth(50);
                    break;

                case HealingPower::Maximum:
                    AddHealth(100);
                    break;
            }
        }

        void LearnAttack(const Attack& attack)
        {
            mAttackList.emplace_back(attack);
        }
        
        std::string GetName() const
        {
            return mName;
        }

        int GetHealth() const
        {
            return mHealth;
        }


        bool IsStillAlive() const
        {
            if (mHealth > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        std::vector<Attack> GetAttackList() const
        {
            return mAttackList;
        }

        void UpgradeAttack(const std::string& attackName)
        {
            auto compareNames = [&attackName](const Attack& a)
                {
                    return a.GetName() == attackName;
                };

            auto itr = std::find_if(this->mAttackList.begin(), this->mAttackList.end(), compareNames);

            if (itr != this->mAttackList.end())
            {
                itr->IncreaseLevel();
            }
        }

        private:
            void AddHealth(int amount)
            {
                if (amount > 0)
                {
                    mHealth += amount;

                    if (mHealth > mMaxHealth)
                    {
                        mHealth = mMaxHealth;
                    }
                }
            }

            void SubtractHealth(int amount)
            {
                if (amount > 0)
                {
                    mHealth -= amount;

                    if (mHealth < 0)
                    {
                        mHealth = 0;
                    }
                }
            }

    private:
        std::string mName{};
        int mHealth{};

        static const int mMaxHealth{ 100 };
        std::vector<Attack> mAttackList;

    protected:

        void CheckCollision()
        {
            std::cout << "Checked Collision\n";
        }
};

class Enemy : public Character
{
    public:
        Enemy(const std::string& name, int health) : Character{name, health}
        {}

        void PerformAttack() override
        {
            std::cout << this->GetName() << " used an attack\n";

            this->CalculateEnemyPathing();
            this->CheckCollision();
        }

    private:

        void CalculateEnemyPathing() const
        {
            std::cout << "Pathing Calculated\n";
        }

};

void GetCharacterInfo(const Character* character);

int main()
{
    auto player = std::make_unique<Enemy>("Hero", 43);
    auto orc = std::make_unique<Enemy>("Orc", 100);
    auto knight = std::make_unique<Enemy>("Knight", 500);

    player->PerformAttack();
    orc->PerformAttack();
    knight->PerformAttack();

    player->LearnAttack({"Assault Rifle", 12, 1});
    player->LearnAttack({"Punch", 3, 1});
    player->LearnAttack({"Tackle", 3, 1});

    orc->LearnAttack({ "Claws", 8, 1 });
    orc->LearnAttack({ "Punch", 3, 1 });
    orc->LearnAttack({ "Tackle", 3, 1 }); 

    knight->LearnAttack({ "Sword", 12, 1 });
    knight->LearnAttack({ "Punch", 3, 1 });
    knight->LearnAttack({ "Tackle", 3, 1 });

    player->UpgradeAttack("Assault Rifle");
    player->UpgradeAttack("Punch");

    player->Heal(HealingPower::Maximum);
    player->PerformAttack();

    GetCharacterInfo(player.get());
    std::cout << '\n';
    GetCharacterInfo(orc.get());
    std::cout << '\n';
    GetCharacterInfo(knight.get());
    std::cout << '\n';
}

void GetCharacterInfo(const Character* character)
{
    std::cout << character->GetName() << '\n';
    std::cout << std::format("{:>4} Health: ", "") << character->GetHealth() << '\n';
    std::cout << std::format("{:>4} Is alive?: ", "") << std::boolalpha << character->IsStillAlive() << '\n';
    std::cout << "Attacks:"  << '\n';

    for (const auto& attack : character->GetAttackList())
    {
        std::cout << std::format("{:>4}", "") << attack.GetName() << " -PWR: " << attack.GetPower() << " - [" << attack.GetLevel() << "]\n";
    }
}
Last edited on
that looks and sounds about right...
Now see if you can get it to do something useful and then try to reuse the code for a slightly different set of requirements. Even in a sample project toying around with it, that will give you a lot to think about, as designs full of abstract classes often make code reuse a huge headache. For example here, say you want to do a new game that has similar characters but... they can't heal. What might that look like? The goal is to reuse it, not rewrite it... :)
Last edited on
Awesome, im glad on on the right track, My understanding is much better now. Im a bit unclear on what you meanby make it useful and reusable, is this close to what you were asking?

In this example I made a character class with a virtual function for the one class and used the character class for the rest since they did not require any specific behavior. There may be other classes that may require this but this is the basics i made just 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
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
#include <iostream>
#include <vector>
#include <string>
#include <memory>

class Character
{
    public:

        enum ClassType
        {
            Nord,
            Khajiit,
            Argonian,
            Breton,
            DarkElf,
            HighElf,
            Imperial,
            Orc,
            Redguard,
            WoodElf
        };

        Character(const std::string& name, int health, int magicka, ClassType classType) : mName(name), mHealth(health), mMagicka(magicka), mClassType(classType)
        {}

        Character() = default;

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

        int GetHealth() const
        {
            return mHealth;
        }

        bool IsAlive() const
        {
            if (mHealth == 0)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        void AddHealth(int amount)
        {
            if (amount > 0)
            {
                mHealth += amount;

                if (mHealth > mMaxHealth)
                {
                    mHealth = mMaxHealth;
                }
            }
        }

        virtual void SubtractHealth(int amount)
        {
            if (amount > 0)
            {
                mHealth -= amount;

                if (mHealth < 0)
                {
                    mHealth = 0;
                }
            }
        }

        std::string GetClassType() const
        {
            switch (mClassType)
            {
                case Character::Argonian:
                    return "Argonian";
                    break;

                case Character::Breton:
                    return "Breton";
                    break;

                case Character::DarkElf:
                    return "Dark Elf";
                    break;

                case Character::HighElf:
                    return "High Elf";
                    break;

                case Character::Imperial:
                    return "Imperial";
                    break;

                case Character::Khajiit:
                    return "Khajiit";
                    break;

                case Character::Nord:
                    return "Nord";
                    break;

                case Character::Orc:
                    return "Orc";
                    break;

                case Character::Redguard:
                    return "Redguard";
                    break;

                default:
                    return "Error";
            }
        }

        int GetMagicka() const
        {
            return mMagicka;
        }

        void SubtractMagicka(int amount)
        {
            if (amount > 0)
            {
                mMagicka -= amount;

                if (mMagicka < 0)
                {
                    mMagicka = 0;
                }
            }
        }

        void AddMagicka(int amount)
        {
            if (amount > 0)
            {
                mMagicka += amount;

                if (mMagicka > mMaxMagicka)
                {
                    mMagicka = mMaxMagicka;
                }
            }
        }

        virtual void GetCharacterInfo() const
        {
            std::cout << GetName() << "'s Class: " << GetClassType() << '\n';
            std::cout << GetName() << "'s health: " << GetHealth() << '\n';
            std::cout << GetName() << "'s magicka: " << GetMagicka() << '\n';
            std::cout << "Is Alive?: " << std::boolalpha << IsAlive() << "\n\n";
        }



    private:
        std::string mName{};
        int mMagicka{};
        ClassType mClassType{};

        static const int mMaxHealth{ 100 };
        static const int mMaxMagicka{ 100 };

    protected:
        int mHealth{};
};

class Khajiit : public Character
{
    public:
        Khajiit(const std::string& name, int health, int magicka, ClassType classType)
            : Character(name, health, magicka, classType), mLives(9)
        {}

        void SubtractHealth(int amount) override
        {
            if (amount > 0)
            {
                mHealth -= amount;

                if (mHealth < 0)
                {
                    mHealth = 0;
                    SubtractLives();
                }
            }
        }

        void SubtractLives()
        {
            --mLives;

            if (mLives < 0)
            {
                mLives = 0;
            }
        }

        void GetCharacterInfo() const override
        {
            std::cout << GetName() << "'s Class: " << GetClassType() << '\n';
            std::cout << GetName() << "'s health: " << GetHealth() << '\n';
            std::cout << GetName() << "'s magicka: " << GetMagicka() << '\n';
            std::cout << GetName() << "'s Lives: " << mLives << '\n';
            std::cout << "Is Alive?: " << std::boolalpha << IsAlive() << "\n\n";
        }

    private:
        int mLives{};
};


int main()
{
    std::unique_ptr<Character> khajiit = std::make_unique<Khajiit>("Hero", 10, 0, Character::Khajiit);

    khajiit->GetCharacterInfo();
    khajiit->AddMagicka(23);
    khajiit->GetCharacterInfo();

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

    Character nord("Hadvar", 100, 100, Character::ClassType::Nord);

    nord.GetCharacterInfo();
}
Last edited on
Pages: 1234