Virtual function

Pages: 1234
Another question :
I notice that the last element is the first to be destroyed and the first one - the last in the queue. Is there an explanation?
Last edited on
Geckoo wrote:
First of all, I don't understand why you got this weird output.

Peter87 did explain that:
the string returned from typeid(type).name() is implementation defined



I notice that the last element is the first to be destroyed

Defined by standard. See https://isocpp.org/wiki/faq/dtors#order-dtors-for-locals

Consider this:
1
2
3
4
5
6
7
8
9
struct A {
  int& x;
  ~A { std::cout << x; }
};

int main() {
  int answer = 42;
  A bad { answer };
}

The bad depends on answer. If answer would be destroyed before bad, then the bad would do bad (well, UB) things in its destructor.
I don't understand why you got this weird output. When I compile, I have a correct output according to the derived structure name

As I said. It's "implementation defined" meaning it can vary between different compilers. There is no "correct output" because the standard leaves it up to the "implementation" (i.e. compiler/standard library) to decide.

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

class A {};

namespace ns
{
	class B {};
}

int main()
{
	std::cout << typeid(int).name() << "\n";
	std::cout << typeid(unsigned int).name() << "\n";
	std::cout << typeid(long long).name() << "\n";
	std::cout << typeid(double).name() << "\n";
	std::cout << typeid(bool).name() << "\n";
	std::cout << typeid(std::string).name() << "\n";

	class C {};

	std::cout << typeid(A).name() << "\n";
	std::cout << typeid(ns::B).name() << "\n";
	std::cout << typeid(C).name() << "\n";
}











For me on Linux with GCC the output is:

i
j
x
d
b
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE



1A
N2ns1BE
Z4mainE1C

Is this better this way?

The code in main() is less complicated and doesn't leak memory so I would say it's "better".

I notice that the last element is the first to be destroyed and the first one - the last in the queue. Is there an explanation?

Ever heard of "the stack"? That's how a stack works. Last in, first out.

Imagine you declared some variable x and then you declared another variable y that keeps a pointer to x.

1
2
X x;
Y y(&x);

It's possible that y will do something to x in its destructor so y better be destroyed before x otherwise you're in trouble.
Last edited on
the string returned from typeid(type).name() is implementation defined
Ok. I understand.
Ever heard of "the stack"? That's how a stack works. Last in, first out.
Oh yes. I forgot the LIFO rule.
Last edited on
I worked hard to find a good example showing how to deal with virtual function. I found something smart - a little game Paper Scissor Stone against the computer. It seems to me interesting because of some static functions/templates and a virtual function according to the player (you or the computer). I share it - maybe it could be useful for some beginners trying to understand those OOP concepts. However if you think that the code could be improved, let me know. You are skilled more than me - and your good advices help a lot ++

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
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <random>

enum class choise { paper, scissor, stone };

struct utilities {

    template <typename T>
    static T rnd(T min, T max)
    {   // avoid a crash if min is greater than max
        if (min > max) std::swap(min, max);

        static std::random_device seeder;
        static std::mt19937 gen(seeder());

        typename std::conditional<std::is_integral<T>::value,
            std::uniform_int_distribution<T>, // for integral
            std::uniform_real_distribution<T> // for real numbers
        >::type distribution(min, max);

        return distribution(gen);
    }

    static choise computerHand()
    {   // only 3 possibilities
        int h = rnd(0, 2);

        if (h == 0) 
            return choise::paper;
        else if (h == 1) 
            return choise::scissor;
        else return 
            choise::stone;
    }
    // check if the player wins against the computer
    static bool checkParty(choise p1, choise p2)
    {
        if (p1 == choise::paper)
        {
            if (p2 == choise::scissor)
                return false;
            if (p2 == choise::stone)
                return true;
        }

        if (p1 == choise::scissor)
        {
            if (p2 == choise::stone)
                return false;
            if (p2 == choise::paper)
                return true;
        }

        if (p1 == choise::stone)
        {
            if (p2 == choise::paper)
                return false;
            if (p2 == choise::scissor)
                return true;
        }
        return false;
    }
    // convert enum to char(s)
    static const char* enumToString(choise ch) throw()
    {
        switch (ch)
        {
        case choise::paper:     
            return "Paper";
        case choise::scissor:   
            return "Scissor";
        case choise::stone:     
            return "Stone";
        }
        return "unexpected";
    }

    static void displayInfo()
    {
        std::cout << "1. Paper" << std::endl;
        std::cout << "2. Scissor" << std::endl;
        std::cout << "3. Stone" << std::endl;
        std::cout << "Choose a hand : ";
    }
};
// base class with our virtual function setChoise
class base {

private:
    short score = 0;

public:
    base(const std::string& name) : m_name(name) {}
    ~base() = default;
    int getScore() { return score; }
    void addScore() { ++score; }

    virtual void setChoice() = 0;

    std::string m_name;
    choise m_choise {};
};

struct player1 : public base {

    player1() : base("Player") {};
    ~player1() = default;
    // rewrite the virtual function for player
    virtual void setChoice()override 
    {
        utilities::displayInfo();
        int num;

        do {    
            std::cin >> num;
            std::cout << std::endl;

            if (num == 1) 
                m_choise = choise::paper;
            if (num == 2) 
                m_choise = choise::scissor;
            if (num == 3) 
                m_choise = choise::stone;
        } while (num != 1 && num != 2 && num != 3);
    }
};

struct player2 : public base {

    player2() : base("Computer") {};
    ~player2() = default;
    // rewrite the virtual function for computer
    virtual void setChoice()override {
        m_choise = utilities::computerHand();
    }
};

int main() {
   
    player1 p1 = player1();
    player2 p2 = player2();

    do {
        std::cout << p1.m_name << " " << p1.getScore() << " : " << p2.m_name << " " << p2.getScore() << std::endl;

        p1.setChoice();
        std::cout << p1.m_name << " plays " << utilities::enumToString(p1.m_choise) << std::endl;

        p2.setChoice();
        std::cout << p2.m_name << " plays " << utilities::enumToString(p2.m_choise) << std::endl;

        if (p1.m_choise == p2.m_choise) {
            std::cout << "Whoa! You got the same hand" << std::endl;
            std::cout << std::endl;

            continue;
        }

        if (utilities::checkParty(p1.m_choise, p2.m_choise))
        {
            std::cout << p1.m_name << " wins this game..." << std::endl; 
            p1.addScore();
        }
        else {
            std::cout << p2.m_name << " wins this game..." << std::endl;
            p2.addScore();
        }
    
        std::cout << std::endl;
     
    } while (p1.getScore() < 3 && p2.getScore() < 3);
    // only three winning games
    std::cout << "Final Score :" << std::endl;
    std::cout << p1.m_name << " has " << p1.getScore() << " points" << std::endl;
    std::cout << p2.m_name << " has " << p2.getScore() << " points" << std::endl;
}


Player 0 : Computer 0
1. Paper
2. Scissor
3. Stone
Choose a hand : 1

Player plays Paper
Computer plays Stone
Player wins this game...

Player 1 : Computer 0
1. Paper
2. Scissor
3. Stone
Choose a hand : 2

Player plays Scissor
Computer plays Stone
Computer wins this game...
Last edited on
score could be part of the base class - and hence so could getScore() and addScore().

As player1 and player2 aren't assigned to a var of base type, there's no polymorphism involved and so virtual isn't needed.

Last edited on
As player1 and player2 aren't assigned to a var of base type, there's no polymorphism involved and so virtual isn't needed.

Thanks. Done for score in the base class.
Can you develop please - just a little bit. What did you mean by "aren't assigned to a var of base type"?
Last edited on
In your program human is human all the way and machine is machine; you never use "player" without knowing its type already when you compile the code.


Unrelated:
1
2
3
    // OOP 
    player1 *p1 = new player1();
    player2 *p2 = new player2();

I have to ask again: Why? Why dynamic memory allocation in this? You don't need it in this program.

Furthemore, the comment "OOP". What does it refer to?
It definitely does not refer to m_name and m_choise.
The base could offer: if ( base::sameHands(p1, p2) )

Please explain this too const std::string &winner = " wins this game...";
I wrote OOP for object. I know what it means, but I agree with you - it is a little bit unclear for other readers. Sorry.

Stack or heap - heap or stack? For this prototype, I did not think about it and I have chosen something quickly, but you are right - this memory allocation is just useless. Changed.

Good point for the sameHands function which I will put in the base class (finally I erased it and I use directly p1.m_choise == p2.m_choise).

I used a const string just to avoid the redondance in the output. Useless too. Changed.

Thanks for all these relevant points.

What did @seeplus means by "aren't assigned to a var of base type"?
Last edited on
What did @seeplus mean by "aren't assigned to a var of base type"?

p1 and p2 are both derived types.
This is defeating the purpose of polymorphism.

Consider this:
1
2
base * p1 = new player1();
base * p2 = new player2();

Now we can call:
1
2
p1->setChoice();
p2->setChoice();

without paying attention to the type of either p1 or p2.
Polymorphism will invoke player1's setChoice() or player2's setChoice() respectively.

Hum... I thought that using a derived class "as root" I could rewrite virtual classes in the base class. According to your explanation, now it seems to me that I had not understood the main concept. In fact, creating an object using the base class B, and associating it with a derived class D, then D can rewrite the virtual classes in B. Right? The polymorphism is done because we have two different functions in the derived classes? In my previous code, I had only a simple inheritance... I had figured out something inverted in my mind according to the relation between parent and child. Thanks for your comment @AbstractionAnon ++
Last edited on
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
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <random>

enum class choise { paper, scissor, stone };

struct utilities {

    template <typename T>
    static T rnd(T min, T max)
    {   // avoid a crash if min is greater than max
        if (min > max) std::swap(min, max);

        static std::random_device seeder;
        static std::mt19937 gen(seeder());

        typename std::conditional<std::is_integral<T>::value,
            std::uniform_int_distribution<T>, // for integral
            std::uniform_real_distribution<T> // for real numbers
        >::type distribution(min, max);

        return distribution(gen);
    }

    static choise computerHand()
    {   // only 3 possibilities
        int h = rnd(0, 2);

        if (h == 0) 
            return choise::paper;
        else if (h == 1) 
            return choise::scissor;
        else return 
            choise::stone;
    }
    // check if the player wins against the computer
    static bool checkParty(choise p1, choise p2)
    {
        if (p1 == choise::paper)
        {
            if (p2 == choise::scissor)
                return false;
            if (p2 == choise::stone)
                return true;
        }

        if (p1 == choise::scissor)
        {
            if (p2 == choise::stone)
                return false;
            if (p2 == choise::paper)
                return true;
        }

        if (p1 == choise::stone)
        {
            if (p2 == choise::paper)
                return false;
            if (p2 == choise::scissor)
                return true;
        }
        return false;
    }
    // convert enum to char(s)
    static const char* enumToString(choise ch) throw()
    {
        switch (ch)
        {
        case choise::paper:     
            return "Paper";
        case choise::scissor:   
            return "Scissor";
        case choise::stone:     
            return "Stone";
        }
        return "unexpected";
    }

    static void displayInfo()
    {
        std::cout << "1. Paper" << std::endl;
        std::cout << "2. Scissor" << std::endl;
        std::cout << "3. Stone" << std::endl;
        std::cout << "Choose a hand : ";
    }
};
// base class with our virtual function setChoise
class base {

private:
    short score = 0;

public:
    base(const std::string& name) : m_name(name) {}
    ~base() = default;
    int getScore() { return score; }
    void addScore() { ++score; }

    virtual void setChoice() = 0;

    std::string m_name;
    choise m_choise {};
};

class player1 : public base {

public:
    player1() : base("Player") {};
    ~player1() = default;
    
private:
    virtual void setChoice()override 
    {
        utilities::displayInfo();
        int num;

        do {    
            std::cin >> num;
            std::cout << std::endl;

            if (num == 1) 
                m_choise = choise::paper;
            if (num == 2) 
                m_choise = choise::scissor;
            if (num == 3) 
                m_choise = choise::stone;
        } while (num != 1 && num != 2 && num != 3);
    }
};

class player2 : public base {

public:
    player2() : base("Computer") {};
    ~player2() = default;

private:
    virtual void setChoice()override {
        m_choise = utilities::computerHand();
    }
};

int main() {
   
    base* p1 = new player1();
    base* p2 = new player2();

    do {
        std::cout << p1->m_name << " " << p1->getScore() << " : " << p2->m_name << " " << p2->getScore() << std::endl;

        p1->setChoice();
        std::cout << p1->m_name << " plays " << utilities::enumToString(p1->m_choise) << std::endl;

        p2->setChoice();
        std::cout << p2->m_name << " plays " << utilities::enumToString(p2->m_choise) << std::endl;

        if (p1->m_choise == p2->m_choise) {
            std::cout << "Whoa! You got the same hand" << std::endl;
            std::cout << std::endl;

            continue;
        }

        if (utilities::checkParty(p1->m_choise, p2->m_choise))
        {
            std::cout << p1->m_name << " wins this game..." << std::endl;
            p1->addScore();
        }
        else {
            std::cout << p2->m_name << " wins this game..." << std::endl;
            p2->addScore();
        }
    
        std::cout << std::endl;
     
    } while (p1->getScore() < 3 && p2->getScore() < 3);
    // only three winning games
    std::cout << "Final Score :" << std::endl;
    std::cout << p1->m_name << " has " << p1->getScore() << " points" << std::endl;
    std::cout << p2->m_name << " has " << p2->getScore() << " points" << std::endl;
}


Overriden functions in the derived classes can be private? Strange - but good ++
Last edited on
It's a bit unintuitive to use a more strict access specifier when overriding so you probably don't want to do that.

1
2
3
4
5
player1 p1;
p1.setChoice(); // This is not allowed ...

base& b = p1;
b.setChoice(); // ... but this is okay!? 

Note that there is nothing wrong with private virtual functions per see as long as it's consistent.
This is polymorphic. Note virtual base destructor.

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
#include <iostream>
#include <algorithm>
#include <random>

enum class choise {
	paper = 0, scissor, stone
};

struct utilities {
	template <typename T>
	static T rnd(T min, T max) {   // avoid a crash if min is greater than max
		static std::mt19937 gen(std::random_device {}());

		if (min > max) std::swap(min, max);

		typename std::conditional_t<std::is_integral_v<T>,
			std::uniform_int_distribution<T>, // for integral
			std::uniform_real_distribution<T> // for real numbers
		> distribution(min, max);

		return distribution(gen);
	}

	static choise computerHand() {   // only 3 possibilities
		const auto h { rnd(0, 2) };

		if (h == 0)
			return choise::paper;

		if (h == 1)
			return choise::scissor;

		return choise::stone;
	}

	// check if the player wins against the computer
	static bool checkParty(choise p1, choise p2) {
		if (p1 == choise::paper)
			return p2 == choise::stone;

		if (p1 == choise::scissor)
			return p2 == choise::paper;

		return p2 == choise::scissor;
	}

	// convert enum to char(s)
	static const char* enumToString(choise ch) {
		switch (ch) {
			case choise::paper:
				return "Paper";

			case choise::scissor:
				return "Scissor";

			case choise::stone:
				return "Stone";
		}

		return "unexpected";
	}

	static void displayInfo() {
		std::cout << "1. Paper\n";
		std::cout << "2. Scissor\n";
		std::cout << "3. Stone\n";
		std::cout << "Choose a hand : ";
	}
};

// base class with our virtual function setChoise
class base {
	short score {};

public:
	base(const std::string& name) : m_name(name) {}
	virtual ~base() = default;

	int getScore() const {
		return score;
	}

	void addScore() {
		++score;
	}

	virtual void setChoice() = 0;

	std::string m_name;
	choise m_choise {};
};

class player1 : public base {
public:
	player1() : base("Player") {};

	void setChoice() override {
		utilities::displayInfo();
		int num {};

		do {
			std::cin >> num;
			std::cout << '\n';

			if (num == 1)
				m_choise = choise::paper;
			else if (num == 2)
				m_choise = choise::scissor;
			else if (num == 3)
				m_choise = choise::stone;
			else
				std::cout << "Invalid\n";

		} while (num != 1 && num != 2 && num != 3);
	}
};

class player2 : public base {
public:
	player2() : base("Computer") {};

	void setChoice() override {
		m_choise = utilities::computerHand();
	}
};

int main() {
	base* p[2] { new player1, new player2 };

	do {
		for (size_t h {}; h < 2; ++h)
			std::cout << p[h]->m_name << " " << p[h]->getScore() << (h ? "\n" : " : ");

		for (size_t h {}; h < 2; ++h) {
			p[h]->setChoice();
			std::cout << p[h]->m_name << " plays " << utilities::enumToString(p[h]->m_choise) << '\n';
		}

		if (p[0]->m_choise == p[1]->m_choise) {
			std::cout << "Whoa! You got the same hand\n\n";
			continue;
		}

		const auto w { utilities::checkParty(p[1]->m_choise, p[0]->m_choise) };

		std::cout << p[w]->m_name << " wins this game...\n\n";
		p[w]->addScore();
	} while (p[0]->getScore() < 3 && p[1]->getScore() < 3);

	// only three winning games
	std::cout << "Final Score :\n";

	for (size_t h {}; h < 2; ++h)
		std::cout << p[h]->m_name << " has " << p[h]->getScore() << " points\n";

	for (size_t h {}; h < 2; ++h)
		delete p[h];
}


PS. Why a struct utilities with just some static functions - why not use a namespace??
Last edited on
I used a const string just to avoid the redondance in the output.

My bad. I did not ask about the string. I did ask about the reference in:
1
2
const std::string &winner = " wins this game...";
                  ^



Hum... I thought that using a derived class "as root" I could rewrite virtual classes in the base class. According to your explanation, now it seems to me that I had not understood the main concept. In fact, creating an object using the base class B, and associating it with a derived class D, then D can rewrite the virtual classes in B. Right?

You refer to B * p1 = new D;?
That does not "create object using the base class B". That does "create (unnamed) class D object" (new D).
It does create a second "object" (p1), but that object is a pointer.
No @keskiverto. I changed the previous code. I had not understood some rules :
https://cplusplus.com/forum/beginner/284974/2/#msg1236198
1
2
player1 *p1 = new player1();
player2 *p2 = new player2();


Hum... I thought that using a derived class "as root" I could rewrite virtual classes in the base class. According to your explanation, now it seems to me that I had not understood the main concept. In fact, creating an object using the base class B, and associating it with a derived class D, then D can rewrite the virtual classes in B. Right?

I thought that it could be better to use a string by its reference, but obviously I was wrong :)

Thank you @seeplus for the new code. I like it. I was not far - but yours is really well done - concise and full of clever tips which short the code, especially the checkParty() function. I used a structure for static functions because it seems to me useful, but if you think that a namespace is better, I will search some explanation about its plus on the web. In my mind, struct is like a class, but with public members as default. What about namespace? I Have to read something about it ++

https://www.geeksforgeeks.org/difference-namespace-class/
Last edited on
I thought that it could be better to use a string by its reference, but obviously I was wrong :)

"Right" and "wrong" is not of interest. Comprehension is.

Can you elaborate what happens in const std::string &winner = " wins this game..."; ?
I used a const because the string will not be changed.
I used a reference because I thought that it is better, but this is the same no?
They do the same work?
1
2
const std::string &winner = "Simple output";
const std::string winner = "Simple output";
No.
I used a const because the string will not be changed.
I used a reference because I thought that it is better, but this is the same no?

The two are similar, but not the same.

Instantiating a std::string as a const is sound practice when you want it to be not modifiable.

Extending the C string constant's lifetime with the reference isn't a good idea.

https://www.learncpp.com/cpp-tutorial/lvalue-references-to-const/

"Simple output" is a C string constant, assignable to a std::string.
Pages: 1234