Correct use of this pointer

Pages: 12
I was following along with a youtube video on how to do some OOP stuff like inheritance and all that, and I was wondering if this is the correct use of the this pointer. I started questioning the guy in the video's knowledge cause he was doing some weird stuff, most of which is still in this code since I copied it from watching the video, I just named it different but I deleted stuff that wasnt used. Here is the video in question:

https://www.youtube.com/watch?v=ZOKLjJF54Xc

Also, is there a good source online, preferably videos of some really good tutorials on C++. mainly OOP stuff? I would like to learn but am Leary of learning bad practices and bad form.

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

class Character
{
public:
	Character(std::string, std::string, std::string, double, double, double, double);
	Character();
	~Character();

	std::string GetName() { return name; }
	void SetName(std::string name) { this->name = name; }

	std::string GetRace() { return race; }
	void SetRace(std::string race) { this->race = race; }

	std::string GetGender() { return gender; }
	void SetGender(std::string gender) { this->gender = gender; }

	double GetHealth() const { return health; }
	double GetMagicka() const { return magicka; }
	double GetCarryWeight() const { return carryWeight; }
	double GetStamina() const { return stamina; }

	void Display();

private:
	std::string name;
	std::string race;
	std::string gender;

	double health;
	double magicka;
	double carryWeight;
	double stamina;
};

Character::Character(std::string name, std::string race, std::string gender,
					double health, double magicka, double carryWeight, double stamina)
{
	this->name = name;
	this->race = race;
	this->gender = gender;
	this->health = health;
	this->magicka = magicka;
	this->carryWeight = carryWeight;
	this->stamina = stamina;
}

Character::Character()
{
	this->name = "Default";
	this->race = "Unknown";
	this->gender = "Unknown";
	this->health = 100;
	this->magicka = 100;
	this->carryWeight = 50;
	this->stamina = 100;
}

Character::~Character()
{
	//std::cout << "\n\nCharacter " << this->name << " was destroyed" << std::endl;
}

void Character::Display()
{
	std::cout << "This chartacters name is " << this->name << ", it's gender is " << this->gender
		<< " and it's race is " << this->race << ".\n\n";

	std::cout << this->name << "'s" << " base health is " << this->health << ", it's base magicka is " <<
		this->magicka << ", it's base carry weight is " << this->carryWeight
		<< " and it's base stamina is " << this->stamina << "." << std::endl;	 
}

class Dragon : public Character
{
	public:
		void MakeSound()
		{
			std::cout << "The Dragon " << this->GetName() << " says " << this->sound << std::endl;
		}
		Dragon(std::string, std::string, std::string, double, double, double, double, std::string);
		Dragon() : Character() {};
		void Display();

	private:
		std::string sound = "Roar";
};

Dragon::Dragon(std::string name, std::string race, std::string gender,
	double health, double magicka, double carryWeight,
	double stamina, std::string sound) : Character(name, race, gender, health, magicka, carryWeight, stamina)
{
	this->sound = sound;
}

void Dragon::Display()
{
	std::cout << "This chartacters name is " << this->GetName() << ", it's gender is " << this->GetGender()
		<< " and it's race is " << this->GetRace() << ".\n\n";

	std::cout << this->GetName() << "'s" << " base health is " << this->GetHealth() << ", it's base magicka is " <<
		this->GetMagicka() << ", it's base carry weight is " << this->GetCarryWeight()
		<< " and it's base stamina is " << this->GetStamina() << "." << std::endl;
	std::cout << this->GetName() << " says " << this->sound << std::endl;
}

int main()
{
	Dragon Mirmulnir("Mirmulnir", "Dragon", "Male", 3071, 150, 350, 200, "Roooaaar");
	Mirmulnir.Display();

	return 0;
}
Last edited on
Ok, your snippet here is technically correct use of the this pointer, it gets to 'this object's' variables to disambiguate between the name collision.
But, if you would just NOT name them the same thing, it would be easier to read and cleaner. Try to NOT use the this pointer at all. It exists because there are times when you must use it, but keep it down to only where necessary, and avoid it for simple things like x = parameter stuff. You can access your class variables and methods directly without the this pointer most of the time, and you should prefer to do it that way.

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
Character::Character(std::string name, std::string race, std::string gender,
					double health, double magicka, double carryWeight, double stamina)
{
	this->name = name;
	this->race = race;
	this->gender = gender;
	this->health = health;
	this->magicka = magicka;
	this->carryWeight = carryWeight;
	this->stamina = stamina;
}

vs:
Character::Character(std::string name_in, std::string race_in, std::string gender_in,
					double health_in, double magicka_in, double carryWeight_in, double stamina_in)
{
	name = name_in;
	race = race_in;
	gender = gender_in;
	health = health_in;
	magicka = magicka_in;
	carryWeight = carryWeight_in;
	stamina = stamina_in;
}

and later:
std::cout << this->  GetName()  ...  this-> is correct technically again but its not needed and ugly


This site has very good practice and format and correct usage on most items.
https://www.learncpp.com/cpp-tutorial/welcome-to-object-oriented-programming/
I can't help with videos, they present 1/10th or less the same material as printed text in the same amount of time for me, but I can read quickly.
Last edited on
I thought so, thank you for clearing that up, and thanks for the link, websites are fine too, i'll take a look through that.

I had some questions as well, im slightly rusty with my oop knowledge. So this is my new 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
#include <iostream>
#include <string>

class Character
{
public:
	Character(std::string, std::string, std::string, double, double, double, double);
	Character();
	~Character();

	std::string GetName() { return name; }
	void SetName(std::string name) { this->name = name; }

	std::string GetRace() { return race; }
	void SetRace(std::string race) { this->race = race; }

	std::string GetGender() { return gender; }
	void SetGender(std::string gender) { this->gender = gender; }

	double GetHealth() const { return health; }
	double GetMagicka() const { return magicka; }
	double GetCarryWeight() const { return carryWeight; }
	double GetStamina() const { return stamina; }

	void Display();

private:
	std::string name;
	std::string race;
	std::string gender;

	double health;
	double magicka;
	double carryWeight;
	double stamina;
};

Character::Character(std::string name_in, std::string race_in, std::string gender_in,
	double health_in, double magicka_in, double carryWeight_in, double stamina_in)
{
	name = name_in;
	race = race_in;
	gender = gender_in;
	health = health_in;
	magicka = magicka_in;
	carryWeight = carryWeight_in;
	stamina = stamina_in;
}

Character::Character() : name("Default"), race("Unknown"), gender("Unknown"), health(100), magicka(100), carryWeight(150), stamina(100)
{
}

Character::~Character()
{
	//std::cout << "\n\nCharacter " << this->name << " was destroyed" << std::endl;
}

void Character::Display()
{
	std::cout << "This chartacters name is " << name << ", it's gender is " << gender
		<< " and it's race is " << race << ".\n\n";

	std::cout << name << "'s" << " base health is " << health << ", it's base magicka is " <<
		magicka << ", it's base carry weight is " << carryWeight
		<< " and it's base stamina is " << stamina << "." << std::endl;	 
}

class Dragon : public Character
{
	public:
		void Speak(std::string sound)
		{
			std::cout << GetName() << ": " << sound << std::endl;
		}
		Dragon(std::string, std::string, std::string, double, double, double, double, std::string);
		Dragon() : Character() {};
		void Display();

	private:
		std::string sound = "Roar";
};

Dragon::Dragon(std::string name, std::string race, std::string gender,
	double health, double magicka, double carryWeight,
	double stamina, std::string sound) : Character(name, race, gender, health, magicka, carryWeight, stamina)
{
	sound = sound;
}

void Dragon::Display()
{
	std::cout << "This chartacters name is " << GetName() << ", it's gender is " << GetGender()
		<< " and it's race is " << GetRace() << ".\n\n";

	std::cout << GetName() << "'s" << " base health is " << GetHealth() << ", it's base magicka is " <<
		GetMagicka() << ", it's base carry weight is " << GetCarryWeight() << " and it's base stamina is " << GetStamina() << "." << std::endl;
}

int main()
{
	Dragon Alduin("Alduin", "Dragon", "Male", 2355, 50, 150, 80, "Goodbye");

	//Alduin.Display();

	Alduin.Speak("Sahloknir, ziil gro dovah ulse! Slen Tiid Vo");

	return 0;
}


Now on line 38, is it better to just do what i did on line 50 instead? like initialize stuff that way instead of inside the body? on line 50 i'm initializing default values but on line 38 im setting it up to take values so I can create it, not sure if it matters or whats better form. In that video he had a separate function initializing all the variables instead of the constructor and that was confusing as well.

Same with all the getters and setters, when i look up info on that particular topic it's pretty polarizing, i get tons of searches of people saying getters and setters are evil, and tons of people saying that getters and setters are standard practice but just be careful with them and dont overuse them, so im a bit confused as to how I should write the character class if getters and setters shouldnt be used.
Last edited on
yes, prefer initializer lists to assignment.
also prefer & on complex types, like string, with a const
Character::Character(const std::string &name_in, const std::string &race_in,
...

some objects need delayed initialization or setting.
one reason is if you need a container of them:
vector <Character> npcs(200); //don't want to set them up here.
...
npcs[0].seteverything(...); //lets fill in one of our characters now.

if you want to use constructors, that is fine, and if you want to do a one by one setter call to do the same thing, that is fine, but its ok to have a 'bulk setter' as well as the individuals if the class design requires it.

getters and setters are indeed standard practice. They don't cost much: the compiler knows to inline them if at all possible, and it almost always is, so its not function overhead. Here is a 10 cent rundown of some of the most basic reasons:
- they don't cost anything
- the setters are where you do your input validation. input validation is a very big deal in professional software.
- all your objects work the same way. You have an object. You can typically assign one of them to another of the same type. You can set the accessible members with setter functions. You can get the data with getter functions. Your code all looks the same. If you mix and match, using setters here and there but not everywhere, you have
type one;
one.x = y;
type two;
two.setz(value); //now you have to remember which class is direct access and which has setters, or look it up over and over. Yuck! You will at times work with dozens of classes or more...

There is a valid argument for not using them at all, if you do not require any input validation or data protection, they are just code bloat and 'in the way'. If you don't use them at all, then don't use them on any classes, so they all have the same interface. But eventually, you WILL need them, and then if you reuse old code without, you are back to the mixed problem. Its generally best to use them all the time. There is no law that you can't do both; if the variable is public scope, you can directly set it OR use the setter for it either way. This is fine as long as the setter does not do anything. Some people will be upset at public members, its a violation of pure OOP design principles, but honestly I think those are kind of boneheaded (they serve their purpose for giant code bases, but sometimes, you have a 10 line program, or you would, if you didn't have to add 500 lines of junk to make it pure).

there are more things you can say about all that, again, just the basic ideas here so it does not fill 5 pages. At the end of the day, you will do it the way the place you work for tells you to do it.
Last edited on
Now on line 38, is it better to just do what i did on line 50 instead? like initialize stuff that way instead of inside the body?

Yes, it's better to use an initialisation list. The way you're doing at line 38 right now, all your data members are being created unitialised (or default-initialised), and then after creation, you're assigning values to them. That's inefficient, and in more complicated classes, could have unintended or undesired side-effects.

If you use an initialiser list, the data members will simply be initialised when created, rather than having this two-step process.

Same with all the getters and setters, when i look up info on that particular topic it's pretty polarizing

Some people dogmatically add them to every class for every data member. Some people dogmatically refuse to use them. I don't believe either of these extremes is sensible.

The important thing is to consider what service your class is providing to code that is going to use it, and design the interface to the class such that it provides that service in a convenient and intuitive way.

If that means that the interface needs to provide a way to set or read the value of a data member, then so be it. If it doesn't, then it's fine not to have that as part of the interface.

If it turns out that your interface needs to be able to get and set every data member, then it's possible you haven't thought about the design of your class properly. It's possilble that all you really want is a POD bundle of values, in which case you can simply use a struct, or make your data members public.

When I'm writing a class that's not POD, and I find that I need the interface to provide the ability to get or set a quantity that's implemented as a data member, then I do prefer to write a getter or setter method, rather than make the data member public. Separating the implementation from the interface makes it easier to change the implementation if you need to.
Now on line 38, is it better to just do what i did on line 50 instead? like initialize stuff that way instead of inside the body? on line 50 i'm initializing default values but on line 38 im setting it up to take values so I can create it, not sure if it matters or whats better form.

Using initialization lists (line 50) is better than initialization inside the function body, then initialization list can prevent unnecessary copies. Realize that without the initialization list the variables will be default initialized before the function body code is executed. Also using initialization lists help to reduce name clashes with the parameters as long the parameters are not used in the body there should be no clash.

Also realize that with the current C++ standards you can also initialize "default" values in the class definition:

1
2
3
4
class test
{
    int variable = 100;
};


You also don't need the destructor in your classes since you're not using dynamic memory.

Same with all the getters and setters, when i look up info on that particular topic it's pretty polarizing, i get tons of searches of people saying getters and setters are evil, and tons of people saying that getters and setters are standard practice but just be careful with them and dont overuse them, so im a bit confused as to how I should write the character class if getters and setters shouldnt be used.


Evil, no, but they are overused and abused. Only provide getters/setters for variables that need to be manipulated outside of other class members. And remember that you don't always need both a setter and a getter in every instance. Often a setter function is not necessary if you always construct the member variables with a constructor.

Also using a getter inside a member function is not the best practice, a member function should directly access the variable unless the getter function is doing some manipulation of the member variable.

Let's look at one of your functions:
1
2
3
4
5
6
7
8
void Dragon::Display()
{
	std::cout << "This chartacters name is " << GetName() << ", it's gender is " << GetGender()
		<< " and it's race is " << GetRace() << ".\n\n";

	std::cout << GetName() << "'s" << " base health is " << GetHealth() << ", it's base magicka is " <<
		GetMagicka() << ", it's base carry weight is " << GetCarryWeight() << " and it's base stamina is " << GetStamina() << "." << std::endl;
}


First I would question whether this function should be part of the class or a "free" function (non class function) for a couple of reasons. IMO, making it a free function helps separate the user interface from the class which is usually a good thing.

Second, would it be better to overload the ostream operator instead? And if the ostream operator is a "friend" you may not need all of the getters at all.




Hello Ch1156,

Here is another option I have seen:
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
class Character
{
public:

private:
	std::string m_name;
	std::string m_race;
	std::string m_gender;

	double m_health;
	double m_magicka;
	double m_carryWeight;
	double m_stamina;
};

Character::Character(std::string name, std::string race, std::string gender,
					double health, double magicka, double carryWeight, double stamina)
{
	m_name = name;
	m_race = race;
	m_gender = gender;
	m_health = health;
	m_magicka = magicka;
	m_carryWeight = carryWeight;
	m_stamina = stamina;
}


Andy
Here is another option I have seen:

That is another controversial option, some people prefer it, some loath it. I think it is better than using "naked" underscores. But I prefer not to obscure my meaningful names with some prefix or suffix.

Handy Andy wrote:
Here is another option I have seen

Just be clear - are you advising the OP to not use an initialisation list, and instead to keep assigning the data members after initialisation?
Last edited on
Thanks for all the replies guys. I have modified my code. The code I wrote above in my OP was copied from a video i watched of someone makign a class, so technically it wasnt my code, but thats why I posted, because it seemed like he was using bad practices and I didnt want to fall into that trap. Here is my current 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
#include <iostream>
#include <string>

class Character
{
public:
	Character(const std::string&, const std::string&, const std::string&, double, double, double, double);
	Character();

	const std::string& GetName() const { return name; }
	void SetName(const std::string& name) { this->name = name; }

	const std::string& GetRace() const { return race; }
	void SetRace(std::string race) { this->race = race; }

	const std::string& GetGender() const { return gender; }
	void SetGender(std::string gender) { this->gender = gender; }

	double GetHealth() const { return health; }
	double GetMagicka() const { return magicka; }
	double GetCarryWeight() const { return carryWeight; }
	double GetStamina() const { return stamina; }

private:
	std::string name;
	std::string race;
	std::string gender;

	double health;
	double magicka;
	double carryWeight;
	double stamina;

	std::string dialogue{};
};

//This will allow me to create a character by entering all the values in myself.
Character::Character(const std::string& name_in, const std::string& race_in, const std::string& gender_in,
	double health_in, double magicka_in, double carryWeight_in, double stamina_in): name(name_in), race(race_in), gender(gender_in),
																					health(health_in), magicka(magicka_in), 
																					carryWeight(carryWeight_in), stamina(stamina_in)
{

}

//And this will create a default one.
Character::Character() : name("NPC"), race("Unknown"), gender("Unknown"), health(100), magicka(100), carryWeight(150), stamina(100)
{
}

class Dragon : public Character
{
	public:
		Dragon(const std::string&, const std::string&, const std::string&, double, double, double, double, const std::string&);
		Dragon(): Character() {};

		void Speak(std::string dialogue)
		{
			std::cout << GetName() << ": " << dialogue << std::endl;
		}

	private:
};

Dragon::Dragon(const std::string& name, const std::string& race, const std::string& gender,
	double health, double magicka, double carryWeight,
	double stamina, const std::string& sound) : Character(name, race, gender, health, magicka, carryWeight, stamina)
{

}

int main()
{
	Dragon Alduin;

	//Alduin.SetName("Alduin");

	Alduin.Speak("Sahloknir, ziil gro dovah ulse! Slen Tiid Vo");

	return 0;
}


I am having a bit of an issue. So I have a character class that creates generic NPC's, now when I create a generic dragon, it says its name is NPC, and I want it to say "Dragon" since this NPC is a very specific type of NPC, a dragon. but I cannot figure out what I'm doing wrong. Also any more tips on how to make the code better is welcome too.

My goal here is to create a sample class with some inheritance that uses all good programming practices and save it in a document so I can go back and look at it later, that way not only do i have a code sample to reference, but the code sample wont have badly written code in it.
Last edited on
Perhaps something like:

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

class Character
{
public:
	Character(const std::string&, const std::string&, const std::string&, double, double, double, double);
	Character();
	Character(const std::string&);

	std::string GetName() const { return name; }
	void SetName(const std::string& name_) { name = name_; }

	std::string GetRace() const { return race; }
	void SetRace(const std::string& race_) { race = race_; }

	std::string GetGender() const { return gender; }
	void SetGender(const std::string& gender_) { gender = gender_; }

	double GetHealth() const { return health; }
	double GetMagicka() const { return magicka; }
	double GetCarryWeight() const { return carryWeight; }
	double GetStamina() const { return stamina; }

private:
	std::string name {"NPC"};
	std::string race {"Unknown"};
	std::string gender {"Unknown"};

	double health {100};
	double magicka {100};
	double carryWeight {150};
	double stamina {100};

	std::string dialogue;
};

//This will allow me to create a character by entering all the values in myself.
Character::Character(const std::string& name_in, const std::string& race_in, const std::string& gender_in,
	double health_in, double magicka_in, double carryWeight_in, double stamina_in) : name(name_in), race(race_in), gender(gender_in),
	health(health_in), magicka(magicka_in),
	carryWeight(carryWeight_in), stamina(stamina_in)
{
}

//And this will create a default one.
Character::Character() {}

Character::Character(const std::string& nam) : name(nam) {}

class Dragon : public Character
{
public:
	Dragon(const std::string&, const std::string&, const std::string&, double, double, double, double, const std::string&);
	Dragon() : Character("Dragon") {};

	void Speak(std::string dialogue)
	{
		std::cout << GetName() << ": " << dialogue << std::endl;
	}

private:
};

Dragon::Dragon(const std::string& name, const std::string& race, const std::string& gender,
	double health, double magicka, double carryWeight,
	double stamina, const std::string& sound) : Character(name, race, gender, health, magicka, carryWeight, stamina)
{}

int main()
{
	Dragon Alduin;

	//Alduin.SetName("Alduin");

	Alduin.Speak("Sahloknir, ziil gro dovah ulse! Slen Tiid Vo");

	return 0;
}

@MikeyBoy,

I said it is an option. I advertise nothing nor did I mean to imply anything other than an option.

Andy
@Handy Andy

So, just to be clear - do you understand why using post-initialisation assignment here is a worse option that using an inialisation list? Do you understand why your "option" shouldn't be recommended to people?
Jonnin made excellent points on the reasons to use setters and getters. I have the opposite feeling. I believe the code should be as complicated as necessary, but not more so. I think getters/setters usually make it more so.

Consider:
1
2
3
4
5
private:
   Foo x;
public:
   Foo getX() const { return x; }
   void setX(Foo f) { x = f; }


This is what 99% of getters and setters look like. yet they add no value over a public member, at the cost of obfuscation and reduced functionality. For example:
- you can't create a pointer or reference to the member, so you've actually reduced functionality.
- You can't do ++ or -- on the member
- You can't assign a value to it and use the result in an expression (like a=b=c)

Sure, there are ways to work around these things, but why should you have to?

So to me, and this is just my opinion, they aren't worth it unless there's a clear need for them.

Chay,

Some things I noticed:

Character(const std::string&, const std::string&, const std::string&, double, double, double, double);

I always give the parameters a name (technically called an identifier)- just so a reader is not wondering what each of the parameters mean. You have given them names in the implementation of the function, but give them in the declaration of the functions as well.

When there are quite a number of parameters, I prefer to format the code with 1 parameter per line:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Character::Character(const std::string& name_in, 
                               const std::string& race_in, 
                               const std::string& gender_in,
	                       const double health_in, 
                               const double magicka_in, 
                               const double carryWeight_in, 
                               const double stamina_in) 
           : name(name_in), 
             race(race_in), 
             gender(gender_in),
	     health(health_in), 
             magicka(magicka_in),
	     carryWeight(carryWeight_in), 
             stamina(stamina_in)
{
}


The good thing about appending _in to the parameter identifiers, is that it identifies those parameters as passing in values. One can also have out parameters if as one of the ways of returning multiple values. IIRC this is mentioned in CPP Core guidelines.

Rather than defining your own default constructor that does nothing, it's better to get the compiler to default it:

Character() = default;

One can do this provided that another constructor has been defined.

You are still using this in your other functions. Avoid doing that.

With the getters & setters, try to think of the functionality the class will provide, and write functions to achieve that behaviour . For example with a Triangle class, we shouldn't be calling a set function 3 times to update the value of each vertex, or worse have 3 set functions to set the vertices. Instead the usual functions for manipulating any kind of Shape are Move, Rotate, Stretch, Mirror for example. So the Move function (as in relocate a Shape to different coordinates, not to be confused with std::move) would update the coords of any vertices for any kind of Shape, so no set functions required.

With your game, you could have an AcquireResource function overloaded for physical things such as a Weapon, or for more ephemeral things like Magicks or Health. If has the type of a physical thing, simply push it into the std::vector of things the player has. If it's type is something like Magicks , then increase the player's current value of that ability.

So what I have described is an abstraction to implement a behaviour.

The other obvious thing that is wrong with public setters is that anyone can use them:

1
2
ChaysBankAccount.SetBalance(-1'000'000.00);
TIMBankAccount.SetBalance(+1'000'000.00);


The same applies to your game, any Character should not be able to change values for any other Character. Can you see that things like race and gender shouldn't change after the object is constructed? And a Character cannot kill another Character by setting it's Health to 0?

The other big piece of advice I can give you, is to start with the simplest possible thing first, get that right, then move on to gradually add more complexity. RPG games can potentially be rather complex so therefore it could be really confusing to try to have stuff going on all at once at the beginning. So start with 1 Player, 1 type of Enemy, 1 type of Weapon and a Health value. Get them to interact by attacking and see the Health value change.

Recently someone said that it was easier to learn what goes on in a simple GUI game, and make simple changes to that, rather than getting involved in all the complexities of RPG games despite the fact that they are console games. It's all the interactions in a RPG that make it hard, whereas a 1980's arcade game like Space Invaders is relatively simple. One could start by changing the colours of things, or the shape of the bullets for example.

Good Luck !!
Ironically in spite of my defense of them, I don't care for getters and setters either. But having had one too many people get their undies in a knot over making stuff public, I go with the flow at my job and leave the darn things off at home. Its good to see that others do this as well.
We go round and round, don't we? With some of the players remaining the same, and some new ones jumping in.

A thread from some years ago: https://www.cplusplus.com/forum/beginner/152947/#msg794572
@OP this rough out is not strictly inheritance. It does however simplify your attempt by breaking the (presumed by me) intent you have. There are lots of things and aspects that can be used to modify this backbone eg operator overloading, updating and displaying scores, designating Roars and other 'sounds' but they are easy to add because each class has easier to identify boundaries.

I haven't concerned myself with indirect addressing blah blah for simplicity only.

Cutting to the chase - forget the esoterica, OOP practice dictates proper attention to access to the data by enabling added control where (generally) necessary. If you don't want that level of control, especially if you're the only user, then miss out on the joy of OOP in C++ and just use structs.

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

class Gender
{
private:
    char m_gender;
public:
    Gender(char g = '?'){m_gender = g;}
    
    std::string display()
    {
        if(m_gender == 'F')
            return "FEMALE";
        else if(m_gender == 'M')
            return "MALE";
        else
            return "OTHER";
    }
};


class Race: public Gender
{
private:
    std::string m_race_name;
public:
    Race(std::string name = "??"){ m_race_name = name; }
    std::string display(){return m_race_name;}
};


class Character: public Race
{
private:
    std::string m_name;
    Race m_race;
    Gender m_gender;
    
    double health{0};
    double magicka{0};
    double carryWeight{0};
    double stamina{0};
    
public:
    Character(std::string name, Race race, Gender gender)
    {m_name = name; m_race = race; m_gender = gender;}
    
    void display()
    { std::cout
        << m_name << ' '
        << m_race.display() << ' '
        << m_gender.display() << '\n';
    }
};



int main()
{
    // SETUP OBJECTS
    Gender male{'M'};
    Gender female{'F'};
    
    Race Dragon_1("Dragon");
    Race Dragon_2("Another Dragon");
    
    Character ABC("Mirmulnir", Dragon_1, male);
    Character XYZ("Mirmulnir Again", Dragon_2, female);
    
    // NOW USE THEM
    XYZ.display();
    ABC.display();
    
    // USE gets AND sets TO MANAGE SCORES ... blah blah
    
    return 0;
}


Mirmulnir Again Another Dragon FEMALE
Mirmulnir Dragon MALE
Program ended with exit code: 0
Last edited on
PS Some of this eg Gender could be a type rather than a class but that's beside the point.
Ok so after reading through comments I have updated the code some more, hopefully this is in the right direction:

Before I get any further with the other suggestions, I added one new class, I wont be adding anymore classes, but the extra class helps me understand things a little better because i can use the Character cass template on both classes, then set one up like the dragon and the others can be default.

I do have one issue though, I already defined all the stuff in character like name, race etc, so why do i need to keep re defining these things in the dragon and nord classes? shouldn't I just have to define new/different behavior for them? or are we getting into polymorphism territory at that point? I don't want to get into PM quite yet, I dont fully understand this yet. I know the basics of PM but still, I got to get this foundation down first. Seems redundant to define things that have already been established, but I dont know.

But every character has health, race etc, but each one has different abilities, like nords have cold resistance etc and dragons can have various ones from frost resistance, fire resistance etc.

What my goal is in my head is I see a base template class which is Character, and from it I can create any NPC either specific like a named main character, or a generic character like you would see just wandering around a town. I want to create the program that's large enough and complex enough, but created with good form and practices that I will get a good sample to look back on when I put it in my notebook. That way I dont have to keep coming back here asking the same stuff over and over again, I can just go look at my notes. But liek I said the program has to be large enough to show all the different elements that an actual game would have in them and not just some quickly created examples, I want to create a real working example to archive. I probably should have mentioned all that in my OP, probably would have helped, sorry about that.

As I mentioned, I like watching videos that teach this stuff, but they take shortcuts and don't flesh stuff out and really explain what's going on and why it's necessary and why you're supposed to do it that way, and I don't know how I'm supposed to learn that way. That video in my OP was good in the sense that he explained what he was doing and why, but he had terrible programming practices like using this pointer for everything, and initializing variables in the body of a class constructor instead of using an initialization list, all of which you can see in my first post cause 99.99% of that code is copied directly from the video, I actually cleaned it up a bit.

If I'm not even shown how things are going to work in the real world, that's like telling a soldier to play call of duty, then expecting him to function on a real battlefield. You need real life exercises(or at least, I do) to fully understand how things are going to play out and interact in a program, and that's what I hope to accomplish with this code below, it's not going to be a full fledged game, but it's going to be a working demonstration to show all the concepts accurately and succinctly without losing information. At least that's what I hope to happen, hopefully that makes some sense, I have a hard time translating my thoughts to words sometimes.

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

class Character
{
public:
	Character
	(
		const std::string& name_in, 
		const std::string& race_in, 
		const std::string& gender_in, 
		double health_in, 
		double magicka_in, 
		double carryWeight_in, 
		double stamina_in,
		const std::string& ability_in
	);
	Character();
	Character(const std::string& defaultName, const std::string& defaultRace);

	std::string GetName() const { return name; }
	void SetName(const std::string& name_) { name = name_; }

	std::string GetRace() const { return race; }
	std::string GetGender() const { return gender; }
	std::string GetAbility() const { return ability; }

	double GetHealth() const { return health; }
	double GetMagicka() const { return magicka; }
	double GetCarryWeight() const { return carryWeight; }
	double GetStamina() const { return stamina; }

	void Speak(const std::string& dialogue)
	{
		std::cout << GetName() << ": " << dialogue << std::endl;
	}

private:
	std::string name{ "NPC" };
	std::string race{ "Unknown" };
	std::string gender{ "Unknown" };

	double health{ 100 };
	double magicka{ 100 };
	double carryWeight{ 150 };
	double stamina{ 100 };

	std::string dialogue{};
	std::string ability{ "None" };
};

//This will allow me to create a character by entering all the values in myself.
Character::Character
(
	const std::string& name_in, 
	const std::string& race_in, 
	const std::string& gender_in,
	double health_in, 
	double magicka_in, 
	double carryWeight_in, 
	double stamina_in,
	const std::string& ability_in
):		name(name_in), 
		race(race_in), 
		gender(gender_in),
		health(health_in), 
		magicka(magicka_in),
		carryWeight(carryWeight_in), 
		stamina(stamina_in),
		ability(ability_in)
{
}

//And this will create a default one.
Character::Character() {}

Character::Character(const std::string& name_in, const std::string& race_in) : name(name_in), race(race_in) {}

class Dragon : public Character
{
	public:
		Dragon
		(
			const std::string& name_in, 
			const std::string& race_in,
			const std::string& gender_in,
			double health_in,
			double magicka_in,
			double carryWeight_in,
			double stamina_in,
			const std::string& ability
		);
		Dragon() : Character("Dragon", "Dragon") {};

	private:
		std::string ability{ "Shout" };
};

Dragon::Dragon
(
	const std::string& name_in, 
	const std::string& race_in, 
	const std::string& gender_in,
	double health_in, 
	double magicka_in, 
	double carryWeight_in,
	double stamina_in, 
	const std::string& ability_in
):		Character(
					name_in, 
					race_in, 
					gender_in, 
					health_in, 
					magicka_in, 
					carryWeight_in, 
					stamina_in,
					ability_in
				 )
{}

class Nord : public Character
{
	public:
		Nord
		(
			const std::string& name_in,
			const std::string& race_in,
			const std::string& gender_in,
			double health_in,
			double magicka_in,
			double carryWeight_in,
			double stamina_in,
			const std::string& ability
		);
		Nord() : Character("Nord", "Nord") {};

	private:
		std::string ability{ "Cold Resistance" };
};

Nord::Nord
(
	const std::string& name_in,
	const std::string& race_in,
	const std::string& gender_in,
	double health_in,
	double magicka_in,
	double carryWeight_in,
	double stamina_in,
	const std::string& ability_in
):		Character(
					name_in,
					race_in,
					gender_in,
					health_in,
					magicka_in,
					carryWeight_in,
					stamina_in,
					ability_in
				 )
{}

void DisplayCharacterInfo(Character& character)
{
	std::cout << "Name: " << character.GetName() << std::endl;
	std::cout << "Race: " << character.GetRace() << std::endl;
	std::cout << "Gender: " << character.GetGender() << std::endl;
	std::cout << "Health: " << character.GetHealth() << std::endl;
	std::cout << "Magicka: " << character.GetMagicka() << std::endl;
	std::cout << "Carry Weight: " << character.GetCarryWeight() << std::endl;
	std::cout << "Stamina: " << character.GetStamina() << std::endl;
	std::cout << "Ability: " << character.GetAbility() << std::endl;
}

int main()
{
	Dragon Alduin("Alduin", "Dragon", "Male", 2355, 50, 150, 80, "Shout");

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

	Alduin.Speak("Sahloknir, ziil gro dovah ulse! Slen Tiid Vo");
	DisplayCharacterInfo(Alduin);


	Nord Ysgramor;

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

	Ysgramor.Speak("Remember this always, son of the north, a Nord is judged not by the manner in which he lived, but the manner in which he died.");
	DisplayCharacterInfo(Ysgramor);
	return 0;
}
Last edited on
Pages: 12