Class not default initializing variables

I cant use my default initialized variables unless I create a function to specifically show them(CreateDinosaur()), why is that? It doesn't work in any class I have, not just this one, is it because the constructor just initializes the variable and doesn't set it?

I call GetDinosaurName() in main as you can see but nothing shows up even though I set it when I created the object. I have to use CreateDinosaur() to be able to display the name and health.

main.cpp

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
#include "pch.h"
#include <iostream>
#include <string>
#include <vector>
#include "Item.h"
#include "Player.h"
#include "Shop.h"
#include "Arena.h"
#include "Dinosaur.h"


int main()
{
	Shop shop;

	shop.AddItems("Golden Necklace of Speak Good", 450);
	shop.AddItems("Helmet of Protect Head", 800);
	shop.AddItems("Staff of Magic Stuff", 8000);
	shop.AddItems("Mask of Suffocation", 420);
	shop.AddItems("Dagger of Major Stabness", 230);

	Player player("Chay", 60000);

	Dinosaur Trex("T-Rex", 800);
	Trex.CreateDinosaur("Trex", 800);

	Trex.GetDinosaurName();

	Trex.AddAttack("Stomp", 50);
	Trex.AddAttack("Chomp", 80);

	Trex.ShowAllDinosaurAttacks();

	std::cout << "\n";

	Dinosaur Velociraptor("Velociraptor", 400);
	Velociraptor.CreateDinosaur("Velociraptor", 400);

	Velociraptor.GetDinosaurName();

	Velociraptor.AddAttack("Bite", 30);
	Velociraptor.AddAttack("Claw", 40);

	Velociraptor.ShowAllDinosaurAttacks();

	//shop.Buy(player);

	return 0;
}



Dinosaur.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
53
54
55
56
57
58
59
60
61
62
63
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <time.h>
#include <stdlib.h>

class Dinosaur
{
	public:
		Dinosaur(std::string dinosaurName, int dinosaurHealth)
			: mDinosaurName(dinosaurName), mDinosaurHealth(dinosaurHealth){}

		void ChooseRandomAttack()
		{
			srand(time(0));

			time_t chooseRandomAttack = rand() % mDinosaurAttacks.size();
			
			//If number is within certain range, perform attack.
		}

		void AddAttack(std::string attackName, int attackPower)
		{
			mDinosaurAttackName = attackName;
			mDinosaurAttackPower = attackPower;

			mDinosaurAttacks.push_back(std::make_pair(attackName, attackPower));
		}

		void CreateDinosaur(std::string dinosaurName, int dinosaurHealth)
		{
			mDinosaurName = dinosaurName;
			mDinosaurHealth = dinosaurHealth;

			std::cout << dinosaurName << " " << dinosaurHealth << "\n";
		}

		void ShowAllDinosaurAttacks()
		{
			for(int i = 0; i < mDinosaurAttacks.size(); i++)
			{ 
				std::cout << mDinosaurAttacks[i].first << " " << mDinosaurAttacks[i].second << "\n";
			}
		}

		std::string GetDinosaurName() { return mDinosaurName; }
		std::string GetDinosaurAttackName() const { return mDinosaurAttackName; }

		int GetDinosaurAttackPower() const { return mDinosaurAttackPower; }

		int GetDinosaurHealth() const { return mDinosaurHealth; }


	private:
		int mDinosaurHealth;
		int mDinosaurAttackPower;

		std::string mDinosaurAttackName;
		std::string mDinosaurName;
		std::vector<std::pair<std::string, int>> mDinosaurAttacks;
};


Also is this the best way of creating a function like this with the variables?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void AddAttack(std::string attackName, int attackPower)
		{
			mDinosaurAttackName = attackName;
			mDinosaurAttackPower = attackPower;

			mDinosaurAttacks.push_back(std::make_pair(attackName, attackPower));
		}

		void CreateDinosaur(std::string dinosaurName, int dinosaurHealth)
		{
			mDinosaurName = dinosaurName;
			mDinosaurHealth = dinosaurHealth;

			std::cout << dinosaurName << " " << dinosaurHealth << "\n";
		}


I dont really like the:

mDinosaurAttackName = attackName;
mDinosaurAttackPower = attackPower;

It seems more like a kludge than a streamlined solution but idk.
Last edited on
I call GetDinosaurName() in main as you can see but nothing shows up even though I set it when I created the object.

GetDinosaurName only returns the name. If you want to print it you should pass the return value to cout's operator <<.

 
std::cout << Trex.GetDinosaurName();
Last edited on
I cant use my default initialized variables unless I create a function to specifically show them(CreateDinosaur()), why is that?


Looks like you don't have any default initialized values. You can't use things that don't exist.

The function createDinosaur should not exist. That's what the constructor is for.

I dont really like the:

mDinosaurAttackName = attackName;
mDinosaurAttackPower = attackPower;

Neither do I. Your constructor should give you a complete, fully initialised, ready to use Dinosaur object. Slowly filling in the necessary values later with other functions is bad.
Last edited on
constructor should give you a complete, fully initialised, ready to use Dinosaur object.

The custom constructor:
1
2
3
Dinosaur::Dinosaur( std::string dinosaurName, int dinosaurHealth )
 : mDinosaurName(dinosaurName), mDinosaurHealth(dinosaurHealth)
{}

Does exactly that.

There is no default constructor in that class. Compiler generates one implicitly only if there are no custom constructors. One can explicitly request the compiler to generate a default constructor.

As Peter said, the problem is/was outside of the class.
...Does exactly that


I disagree. The dinosaur objects are useless without attacks, but those are set separately. It is possible to call ChooseRandomAttack() on a dinosaur object that has no attacks. It is possible to create a dinosaur object that is not fully initialised and ready to use.

As Peter said, the problem is/was outside of the class.

The problem of not outputing a value to screen was outside the class. The problem of the class being badly written and the problem of the OP talking about default initialised variables that don't actually have any default values is inside the class.

The OP is clearly aware that doing it separately is an ugly kludge.
Last edited on
Ok so I re-tooled the class and main, I think it's better but IDK you guys tell me:

main.cpp

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
#include "pch.h"
#include <iostream>
#include <string>
#include <vector>
#include "Item.h"
#include "Player.h"
#include "Arena.h"
#include "Shop.h"
#include "Dinosaur.h"


int main()
{
	Shop shop;
	Arena arena;

	shop.AddItems("Golden Necklace of Speak Good", 450);
	shop.AddItems("Helmet of Protect Head", 800);
	shop.AddItems("Staff of Magic Stuff", 8000);
	shop.AddItems("Mask of Suffocation", 420);
	shop.AddItems("Dagger of Major Stabness", 230);

	Player player("Chay", 60000);

	std::vector<std::pair<std::string, int>> trexAttacks, velociraptorAttacks;


	//Create T-Rex

	trexAttacks.push_back(std::make_pair("Stomp", 80));
	trexAttacks.push_back(std::make_pair("Crunch", 30));

	Dinosaur Trex("T-Rex", 800, trexAttacks);

	std::cout << Trex.GetDinosaurName() << '\n';

	Trex.ShowAllDinosaurAttacks();

	std::cout << "\n";


	//Create Velociraptor

	velociraptorAttacks.push_back(std::make_pair("JumpAttack", 20));
	velociraptorAttacks.push_back(std::make_pair("Bite", 15));

	Dinosaur Velociraptor("Velociraptor", 400, velociraptorAttacks);

	std::cout << Velociraptor.GetDinosaurName() << '\n';

	Velociraptor.ShowAllDinosaurAttacks();

	std::cout << "\n";


	//Show list of Arena opponents (All possible enemies the player could face)

	arena.CreateListOfOpponents(Trex, trexAttacks);
	arena.CreateListOfOpponents(Velociraptor, velociraptorAttacks);

	//Choose and enemy randomly

	std::cout << "\n";

	arena.RandomlyChooseEnemy();

	//shop.Buy(player);

	return 0;
}



Dinosaur.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
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <time.h>
#include <stdlib.h>

class Dinosaur
{
	public:
		Dinosaur(std::string dinosaurName, int dinosaurHealth, std::vector<std::pair<std::string, int>> dinosaurAttacks)
			: mDinosaurName(dinosaurName), mDinosaurHealth(dinosaurHealth), mDinosaurAttacks(dinosaurAttacks){}

		void ChooseRandomAttack()
		{
			srand(time(NULL));

			unsigned int chooseRandomAttack = rand() % mDinosaurAttacks.size();
			
                        //TO DO
			//If number is within certain range, perform attack.
		}

		void ShowAllDinosaurAttacks()
		{
			for(unsigned int i = 0; i < mDinosaurAttacks.size(); i++)
			{ 
				std::cout << mDinosaurAttacks[i].first << " " << mDinosaurAttacks[i].second << "\n";
			}
		}

		std::string GetDinosaurName() const { return mDinosaurName; }
		std::string GetDinosaurAttackName() const { return mDinosaurAttackName; }

		int GetDinosaurAttackPower() const { return mDinosaurAttackPower; }

		int GetDinosaurHealth() const { return mDinosaurHealth; }


	private:
		int mDinosaurHealth;
		int mDinosaurAttackPower;

		std::string mDinosaurAttackName;
		std::string mDinosaurName;
		std::vector<std::pair<std::string, int>> mDinosaurAttacks;
};
Last edited on
My bad. Focused on CreateDinosaur and skipped the rest.


There is more:
1
2
3
4
5
6
7
8
9
10
11
12
private:
  int mDinosaurHealth; // #1
  int mDinosaurAttackPower; // #2
  std::string mDinosaurAttackName; // #3
  std::string mDinosaurName; // #4
  std::vector<std::pair<std::string, int>> mDinosaurAttacks; // #5
};

Dinosaur::Dinosaur( std::string dinosaurName, int dinosaurHealth )
 : mDinosaurName(dinosaurName), // #4
   mDinosaurHealth(dinosaurHealth) // #1
{}

1. The order of initialization of members is dictated by the order in the definition.
A compiler should warn about this constructor.

Fixing and adding the implicit ones:
1
2
3
4
5
6
7
Dinosaur::Dinosaur( std::string dinosaurName, int dinosaurHealth )
 : mDinosaurHealth(dinosaurHealth),
   mDinosaurAttackPower(),
   mDinosaurAttackName(),
   mDinosaurName(dinosaurName),
   mDinosaurAttacks()
{}



2. What is the logic of having both {mDinosaurAttackName, mDinosaurAttackPower} and the list of mDinosaurAttacks?
* ShowAllDinosaurAttacks() shows whole list. Good.
* GetDinosaurAttackPower() returns either value from latest call to AddAttack() or the default.
* ChooseRandomAttack() does nothing?

One could have a simpler cache, for example:
1
2
3
4
5
6
7
8
9
10
11
12
13
private:
  size_t current {};
  std::vector<std::pair<std::string, int>> mDinosaurAttacks;
};

ChooseRandomAttack() {
  current = ...
}

GetDinosaurAttackPower() {
  if ( mDinosaurAttacks.empty() ) return 0;
  else return mDinosaurAttacks[current].second;
}



Edit:
There are at least three possibilities:

1. A newborn dinosaur has no attacks. It can learn some. You have to handle the null set.

2. Every dinosaur has one (same default) attack. They can learn some more.

3. Every dinosaur is born with all attacks. They could still learn more. Constructor requires a list.
Last edited on
1. The order of initialization of members is dictated by the order in the definition.


Wait so the order the variables are declared in the private section, they have to be in that order in the class constructor?

1
2
3
4
5
6
7
Dinosaur::Dinosaur( std::string dinosaurName, int dinosaurHealth )
 : mDinosaurHealth(dinosaurHealth),
   mDinosaurAttackPower(),
   mDinosaurAttackName(),
   mDinosaurName(dinosaurName),
   mDinosaurAttacks()
{}


I'm slightly perplexed by this as well, don't you have to define ALL variables in the constructors argument list to be able to initialize the variables?

A compiler should warn about this constructor.


Mine doesn't, I'm using MVS 2017, I just switched to it from code blocks a few days ago so i'm unsure if I have to tell it to display those warnings.

What is the logic of having both {mDinosaurAttackName, mDinosaurAttackPower} and the list of mDinosaurAttacks?


Now that I think about it it is a little strange, my initial logic was that to be able to add an attack to the vector, the attack has to exist first, and thats why I created those variables, but now I see that an attack name and power could just be added directly to the vector since mDinosaurAttacks IS both the attack and attack power variables, the other two could have just been local variables in main, probably not even that.

* ChooseRandomAttack() does nothing?


I havent gotten to adding it quite yet. I do have a function in Arena that randomly chooses opponents and it will function exactly the same:

1
2
3
4
5
6
7
8
void RandomlyChooseEnemy()
{
	srand(time(0));

	int randomlyChooseOpponent = rand() % ShowListOfOpponents().size();

	std::cout << "Opponent: " << mOpponents[randomlyChooseOpponent].GetDinosaurName() << " was chosen!!\n";
}


Wait so the order the variables are declared in the private section, they have to be in that order in the class constructor?


No. You can put them in any order you like in the constructor's initialiser, but no matter what order you write them in, they will be initilalised in the order they were declared.

1
2
3
4
5
6
7
8
9
10
11
12
class example
{
  int x;
  int y;
  int z;
};

example::example () : 
  x(3),  // x will be initialised first
  y(4), //  y will be initialised second
  z(5) //  z will be initialised third
{}


However, see how the initialisation order changes in this alternative writing of the constructor:
1
2
3
4
5
example::example () : 
  y(3),  // y will be initialised second
  z(4), //  z will be initialised third
  x(5), //  x will be initialised first
{}

That's right; it doesn't change.

don't you have to define ALL variables in the constructors argument list to be able to initialize the variables?
No.
Last edited on
Maybe I am out of place here but you could define a base dinosaur class and then expand with other classes that inherit from the base dinosaur class. So you would have a trex class that is a child of dinosaur class.
However, see how the initialisation order changes in this alternative writing of the constructor:
1
2
3
4
5
example::example () : 
  y(3),  // y will be initialised second
  z(4), //  z will be initialised third
  x(5) //   x will be initialised first
{}

Furthermore, the subobjects are usable when they have been initialized, but not before:
1
2
3
4
5
6
7
example::example () : 
  x(3),
  // x is complete and can be used
  // x and y are complete and can be used
  z(x+2) // use x to initilize z
  //  all bases and members have been initialized
{}


However, if your constructor's initializer list looks "out of order", you might be tempted:
1
2
3
4
example::example () : 
  z( 4 ),
  x( z - 1) // Error: the z is still uninitialized
{}

The compiler could (GCC does) warn about the disorder in "safe cases":
: z( 4 ), x( 3 )
so that you won't later descend to logical errors.

Besides, does this "correct" code look intuitive?
1
2
3
4
example::example () : 
  z(x+2),
  x(3)
{}
Topic archived. No new replies allowed.