Polymorphism and Base Classes

I'm actually going to try and leave this code free, for your benefit, because both headers have roughly 50+ lines each. Not that I'm trying to insult your ability to read or "hide" my code, but because I don't know where to begin/what to paste. If you want it, ask and I will, but for simplicity...

Basically, I'm trying to play with C++ (what I should have been doing all along) instead of bookworm it, and remembering back through Programming Principles and Practice, I decided I wanted to work on inheritance and polymorphism. I wanted to make a framework for a game that had characters because... well, it's funner than class B:public A and whatnot, and is a more "practical" program.

At this moment I have a Human class derived from a Character class which - at least I think, and I'd love your opinion - was created solely for the purposes of listing basic stats that ANY creature would have (and could have been called that just as well). I'm to the point now I want to make it a pure base class, but I don't know how I should approach that, and as I'm to understand there are options:

- Pure virtual functions
- Protecting the constructor(s)

I'm also to understand pure virtual functions are the preferred method, so I'd like to follow tradition, but I don't know how many I should provide, and even WHAT they should be.

I was thinking that I wanted to make an easy way to print statistics, so I created two output_attributes functions(), one for cout, and one for any ostream. All good, but SHOULD one of those/both of those/EVERY function be pure virtual? Should the other functions (operators mostly, but of course I have get() functions for individual stats) be virtual in ANY capacity?

I realize virtual functions, polymorphism, and inheritance are important aspects, so I just want to do it right and not learn bad habits. Also, it's excellent (and surprisingly fun) practice. I AT LEAST feel good about the character base class. I feel like I made a good call on that - if I want to make a dwarf or an elf or a hobbit or just anything Tolkien ever thought of, the Character class is a solid start, but everything else I'm iffy on.
Also, if you INSIST on seeing the code (and I haven't been forthright - another reason is I'm slightly embarrassed by my code) then I'll oblige you in all its lack of glory, complete with comments I made for myself... and yes, I DO write comments to myself in the manner you'll see in Character.h:

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
//I want to be VERY clear that Character is intended to be a base class, but I'm not certain
//how/where to implement that concretely. Pure virtual functions seem the likely and
//popular solution, but there is always protecting the constructor.

//That also brings up, "What should and shouldn't be protected?" Also, if we're using
//virtual functions (polymorphism), what/how many members should be virtual?

#pragma once
#include <string>
#include <iostream>

class Character {
public:
	Character();
	Character(short strength, short vitality, short dexterity);
	Character(const Character&) = delete; //Copy constructor disabled

	~Character();

	short get_str() const { return str; }
	short get_vit() const { return vit; }
	short get_dex() const { return dex; }
	int get_hp() const { return hp; }

	//Managing additions to attributes made easier
	virtual void output_attributes() const;
	virtual void output_attributes(std::ostream& os) const;

	virtual std::ostream& print_information(std::ostream& os) const;
	virtual void print_information() const;

	bool operator==(const Character& ch);
	bool operator!=(const Character& ch);

	//I THINK THESE ARE LIKELY USELESS_________________________________________
	virtual std::ostream& operator<<(std::ostream& os);
	virtual void operator<<(const Character ch);
private:
	//Character(const Character&); //IF PRIVATE, this also prevents copying.
	//Should implement a luck stat later.
	short str;
	short vit;
	short dex;
	int hp;
};


bool operator==(const Character& a, const Character& b);
//bool operator!=(const Character& a, const Character& b); //Perhaps a bad idea
void char_compare(const Character& a, const Character& b);

constexpr short def_params{ 5 }; //Just because the book at this point uses them. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include "Character.h"
using namespace std;

Character::Character()
	:str{ def_params }, vit{ def_params }, dex{ def_params }, hp { def_params * 10 }
{
	cout << __func__ << " CONSTRUCTING DEFAULT CHARACTER\n";
}

Character::Character(short strength, short vitality, short dexterity)
	: str{ strength }, vit{ vitality }, dex{ dexterity }, hp {
	vitality * 10
}
{
	if (strength < 1 || strength > 10) {
		cerr << "Error: strength parameter must be between 1 and 10\n";
		exit(1);
	}
	if (vitality < 1 || vitality > 10) {
		cerr << "Error: vitality parameter must be between 1 and 10\n";
		exit(1);
	}
	if (dexterity < 1 || dexterity > 10) {
		cerr << "Error: dexterity parameter must be between 1 and 10\n";
		exit(1);
	}
	//I'm unsure how to kill a player or NPC, so I'm trying this...
	if (hp <= 0) {
		cout << "You have died!\n";
		exit(1);
	}
	cout << __func__ << " CONSTRUCTING CHARACTER\n";
}

Character::~Character()
{
	cout << __func__ << " DESTRUCTING\n";
}

//Make changes for output here and the following ostream version
void Character::output_attributes() const
{
	cout << "RACE: UNKNOWN\n";
	cout << "Health:\t\t\t" << get_hp() << endl;
	cout << "Strength:\t\t" << get_str() << endl;
	cout << "Vitality:\t\t" << get_vit() << endl;
	cout << "Dexterity:\t\t" << get_dex() << endl;
}

void Character::output_attributes(std::ostream& os) const
{
	os << "RACE: UNKNOWN\n";
	os << "Health:\t\t\t" << get_hp() << endl;
	os << "Strength:\t\t" << get_str() << endl;
	os << "Vitality:\t\t" << get_vit() << endl;
	os << "Dexterity:\t\t" << get_dex() << endl;
}

std::ostream& Character::print_information(std::ostream& os) const
{
	os << "******************************\n";
	output_attributes(os);
	os << "******************************\n";
	return os;
}

void Character::print_information() const
{
	cout << "******************************\n";
	output_attributes();
	cout << "******************************\n";
}

//May conflict with other ostream operators that inherit Character
//I THINK THESE ARE LIKELY USELESS_____________________________________________
std::ostream& Character::operator<<(std::ostream& os)
{
	return print_information(os);
}

void Character::operator<<(const Character ch)
{
	ch.print_information();
}
//_____________________________________________________________________________
bool Character::operator==(const Character& ch)
{
	return(hp == ch.hp && str == ch.str && vit == ch.vit);
}

bool Character::operator!=(const Character& ch)
{
	return(!(hp == ch.hp && str == ch.str && vit == ch.vit));
}

bool operator==(const Character& a, const Character& b)
{
	return(a.get_hp() == b.get_hp() && a.get_str() == b.get_str() &&
		a.get_vit() == b.get_vit());
}

bool operator!=(const Character& a, const Character& b)
{
	return(!(a.get_hp() == b.get_hp() && a.get_str() == b.get_str() &&
		a.get_vit() == b.get_vit()));
}

void char_compare(const Character& a, const Character& b)
{
	if (a == b) {
		cout << "Characters are equal\n";
		return;
	}
	else cout << "Characters are unequal\n";
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma once
#include "Character.h"


class Human :public Character {
public:
	Human(short str, short vit, short dex, std::string n);
	Human(std::string n);

	std::string get_name() const { return name; }

	virtual std::ostream& print_information(std::ostream& os) const override;
	virtual void print_information() const override;

	//I THINK THESE ARE LIKELY USELESS_________________________________________
	std::ostream& operator<<(std::ostream& os) override;
	void operator<<(const Human h);
private:
	std::string name;
};


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
#include "Human.h"
using namespace std;

Human::Human(std::string n)
	: Character{}, name{ n }
{
	cout << __func__ << "CONSTRUCTING DEFAULT HUMAN\n";
}

Human::Human(short str, short vit, short dex, std::string n)
	:Character{ str, vit, dex }, name{ n }
{
	cout << __func__ << "CONSTRUCTING HUMAN\n";
}

std::ostream& Human::print_information(std::ostream& os) const
{
	os << "******************************\n";
	os << __func__ << " properly overrode\n";
	os << "RACE: HUMAN\n";
	os << "Name:\t\t\t" << get_name() << endl;
	os << "Health:\t\t\t" << get_hp() << endl;
	os << "Strength:\t\t" << get_str() << endl;
	os << "Vitality:\t\t" << get_vit() << endl;
	os << "Dexterity:\t\t" << get_dex() << endl;
	os << "******************************\n";
	return os;
}

void Human::print_information() const
{
	cout << "******************************\n";
	cout << __func__ << " properly overrode\n";
	cout << "RACE: HUMAN\n";
	cout << "Name:\t\t\t" << get_name() << endl;
	cout << "Health:\t\t\t" << get_hp() << endl;
	cout << "Strength:\t\t" << get_str() << endl;
	cout << "Vitality:\t\t" << get_vit() << endl;
	cout << "Dexterity:\t\t" << get_dex() << endl;
	cout << "******************************\n";
}

//I THINK THESE ARE LIKELY USELESS_____________________________________________
std::ostream& Human::operator<<(std::ostream& os)
{
	os << __func__ << " properly overrode\n";
	return print_information(os);
}

void Human::operator<<(const Human h)
{
	h.print_information();
}


NOTE: I haven't updated Human for printout as I did character. Also yes, I'm including constructor/destructor output to see how it all comes together. Also yes, I did it for virtual functions as well to see if they were being overridden. No, I'm still not 100% convinced I'm using inheritance and overriding them correctly.
Last edited on
Topic archived. No new replies allowed.