Creating different weapons in class

Pages: 1234
Obviously getters and setters are necessary for certain situations, I dont know which situations though.

This comes down to thinking about what your class is actually for. When you create a class, you need to decide what its purpose is. What responsibilities will your class have? What services will your class provide to other code?

Once you've decided that, then you can design the interface of the class in such a way that it presents those services in the most useful way to other code. That may mean that you want getters and setters for a given data member, or it may not. It depends on what your class is actually doing.
Last edited on
1
2
3
4
void SetHealth(int health)
{
    m_health = health;
}

In this case a setter doesn't make much sense. The common uses for setters are when you need to check the input - for example assert(health >= 0);
Another common scenario is to fire a change event if you implement the observer pattern
So how would I access and change the variables in my class? Is there any value in

Also what does:

"Another common scenario is to fire a change event if you implement the observer pattern"

that mean?

And using a setter to check input??? how does that work?
Thomas1965 wrote:
In this case a setter doesn't make much sense.


Not sure what you meant there, it trivial but often still necessary, have to have some way of accessing a private variable.

Thomas1965 wrote:
Another common scenario is to fire a change event if you implement the observer pattern


I think there are a number of concepts the OP has to get down before we start talking about Design Patterns :+)

Hi Chay,

You are doing the right thing by having a set function. But the question from you was why do we do this?

It is good to have member variables private, that is so constructors can initialize them and only member functions can change their value.

The advantage of having a function to set a member variable's value is that it can perform checks, for example ask for a PIN number for a bank account. Basically we want to see if the arguments to the set function make sense

Another advantage is if the underlying representation changes, as in an angle being stored as degrees , is changed to radian, then the set function arguments can remain the same, so it is the same for whoever is calling the function. In other words it doesn't break the caller's code. You see, what happens is that people/ organizations write code (a library) which they give to their clients, who then use the classes / functions from the library in their own code which could be millions of lines long. It would be bad if the client had to change all their code just because someone wanted to make a minor change to the library. So we always strive to write code in such a way that we can change functions internally without changing the way the function is called.

The alternative to not having private variables is to have the variables public, which is obviously bad. There are some situations where we can have a fully public struct, which is then used as a type for a private member of a class.

Even though the SetHealth is trivial, it is still worth having. BUT, one shouldn't blindly have a get and set function for every member variable, the class may as well be a struct with default public access. There are several ways to think about this:

* A set function is only really needed if a variable is going to change after it is constructed.

* It may be possible to have one function that does something to an object, changing the value of several member variables, instead of requiring the client to call several functions to individually change the member variables. Say I had a Triangle object, I could a Move function instead of requiring the client to call 3 functions to move each vertex.

This all gets back to what MikeyBoy was saying about class interface (basically public functions that allow the user to interact with the object)

Also I think one problem might be plunging in and writing a bunch of code, then wondering whether it is right or not. Instead, ask your self the questions MikeyBoy mentioned, write things down using pencil and paper. Keep it simple, start with just one action the thing can do, make that function as simple as printing something on screen. Also have one member variable.

Here you go, 2 little Exercises:
Only do literally what I have asked for here, it should be simple now, we can add more functionality once you get this down.
Exercise 1:
Create a class named Pistol.
Specify a default constructor.
Have member function named Fire, which prints "Bang!" on the screen.
Create an object of type Pistol
Call the Fire function 5 times.

Exercise 2:

Modify the code from exercise 1, to do the following:

Create a member variable which keeps track of the number of bullets in the Pistol. What type should this variable have?
Make sure this member variable is initialized in a constructor. Provide for a default value of 10.
Write another constructor which allows the user to decide how many bullets the Pistol will have
Every time the Fire function is called, decrement the number of bullets variable. Print on screen how many bullets are left.
If there are no bullets left, print a message on the screen, end the program.

We look forward to seeing your code :+)
So how would I access and change the variables in my class?

The idea is you make the member variables public by default.

The primary purpose of mutator functions ("setter functions") are to prevent changes that would violate some assumption. For example, consider a class Player: if I wanted to express that "every player has a health value", I could say something natural like
 
struct Player { int health; };

Which is about as simple as can be. I can set a player's health to whatever I want by saying p.health = whatever.

But if I chose to define each player as having an integer health value in the interval [0, 100], then I should certainly make that assumption explicit, or perhaps even enforce that it is always true. This is important, because by definition, if some object of type Player violates that condition by having a health out-of-bounds, that object doesn't actually represent a valid player any more. Conceptually speaking, anything could happen.

We'd call that assumption (i.e., that the health is always in [0, 100]) a class invariant. While the first definition of Player wouldn't stop us from just saying Player.health = 200 and breaking things, we could write the class to guarantee that condition using a get/set pair:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Player 
{
  void set_health(int health_) 
  {
    // If the new health is invalid
    if (health_ < 0 || health_ > 100) // then cause an error
      throw std::invalid_argument("new health out of bounds"); 

     // otherwise go ahead and set the health
     health = health_;
  }  
  int get_health() const noexcept { return health; }
private:
  int health = 0;
}

This way, nobody outside the class can forget about the rule and change the health to something out-of-bounds - they'll get an error, and no change will be made. But if there is no such invariant, then usually such code is just a waste of time.
Last edited on
There are 2 sides to this: the validity of data in a set function; and who has the right to change member variables.

For the second one, we probably don't want anyone (including Enemies) changing the Player's health. I would propose that set_health would be a private function, called when there is some kind of attack or other negative scenario, or when some kind restorative thing is found / applied.

This is why we have the concept of encapsulation:

https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)

As a side note Player health is good example why having functions to interact with member variables is a good idea. I play Skyrim too, the Player's health / ability is affected by things such as time since last sleep, last meal, as well as injuries.

But Max is right, there are scenarios where using a plain struct is fine.
Ok, thanks for your responses, here is what I have for the exercises:

Exercise #1:

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


class Pistol
{
    public:
        Pistol() = default;

        void Fire()
        {
            std::cout << "Bang" << std::endl;
        }

    private:

};

int main()
{
    Pistol pistol;

    pistol.Fire();
    pistol.Fire();
    pistol.Fire();
    pistol.Fire();
    pistol.Fire();

    return 0;
}




Exercise #2:


I'm a little stuck on this one, i'm stuck on:

Write another constructor which allows the user to decide how many bullets the Pistol will have.


I cant overload the constructors, do you want me to modify the current constructor? I'm unsure. The rest of the exercise is easy, I just don't get this part.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>


class Pistol
{
    public:
        Pistol(int bullets): m_bullets(bullets){}

        void Fire()
        {
            std::cout << "Bang" << std::endl;
        }

    private:
        int m_bullets;
};

int main()
{
    Pistol(10);

    return 0;
}
Constructors may be overloaded.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

struct pistol
{
    pistol() = default ; // 3 bullets (uses default member initialiser)

    // overloaded constructor (non-converting)
    // explicit: https://en.cppreference.com/w/cpp/language/converting_constructor
    explicit pistol( int n_bullets ) : num_bullets( n_bullets < 0 ? 0 : n_bullets ) {}

    void fire() { for( ; num_bullets > 0 ; --num_bullets ) std::cout << "bang!\n" ; }

    void load( int n_bullets ) { if( n_bullets > 0 ) num_bullets += n_bullets ; }

    private: int num_bullets = 3 ; // default member initialiser
                                   // http://www.stroustrup.com/C++11FAQ.html#member-init
};
Edit: I didn't see JLBorges post while writing mine :+)

Ok, good work :+)

I didn't specify these things explicitly, but you could make these changes. It is not supposed to be criticism, seen as I asked you to only do the things I literally mentioned.

With Exercise 1:

Use a for loop to call the Fire function. Use a constant ( constexpr )variable for limit as to how many times it runs.

Rather than using std::endl , just put a \n in the string, as in :

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

std::endl can be slow if used a lot.

You may as well get used to separating the code into *.hpp *.cpp and main.cpp files.

The Fire function should be const as well, at the moment it doesn't change the value of any member variables, that is it doesn't change the state. This will change once Ex 2 is complete.

Fire could be marked noexcept, it is not throwing any exceptions


With exercise 2:

The idea is to modify the original code, we will keep doing this. So incorporate the things from Ex 1, and do the other things in Ex 2

I cant overload the constructors


Yes you can. A constructor is a function like any other it can be overloaded. The compiler determines which it going call by the number and type of the arguments.

Can you tell what might be wrong with the int type for the number of bullets? Edit This is still a question after JLBorges post.

Naming variables and functions is an important thing in C++. bullets is not really descriptive, how about BulletCapacity ?

My preference for naming member variables, is to not prepend or append anything to the name. I do append Arg to the identifier in function parameter though:

Pistol(const ??? BulletCapacityArg): BulletCapacity(BulletCapacityArg){}

The ??? is for you to put in a better type than int.

I added the const, because we are going to get the compiler to enforce the idea we aren't changing the value of BulletCapacityArg

One can initialize member variables at the declaration (since C++11), it is the same as using the member initialization list, and is a good idea for default values.


1
2
3
4
public:
        Pistol() {} 
private:
        const ??? BulletCapacity = 10;  // put a better type 


Note that is different to assigning in the constructor body.

You can still use the member initialization list when there is an argument to a constructor.
Last edited on
I finished it and I havent read your post above but will post this and do so 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
#include <iostream>


class Pistol
{
    public:
        Pistol() = default;
        explicit Pistol(int bullets) : m_bullets(bullets){}

        void Fire()
        {
            std::cout << "Bang" << std::endl;
            if(m_bullets > 0)
            {
                m_bullets -= 1;
                std::cout << "The weapon has " << m_bullets << " rounds left." << std::endl;
            }
            else if(m_bullets == 0)
            {
                std::cout << "The weapon has run out of ammunition" << std::endl;
                std::cout << m_bullets << " rounds left!" << std::endl;
            }
        }

    private:
        int m_bullets = 10;
};

int main()
{
    Pistol(10);
    Pistol pistol;

    for(int i = 0; i < 11; i++)
    {
        pistol.Fire();
    }


    return 0;
}


Can you tell what might be wrong with the int type for the number of bullets?


Ah, it should be unsigned, since you cant have a negative amount of ammo, only 0.
Last edited on
Ah, it should be unsigned, since you cant have a negative amount of ammo, only 0.

Ok. Can you tell us what the 4 different unsigned types are? Which would you choose, and why?

In the Fire function, it's first action is to fire, without checking if there are any bullets.

You could modify JLBorges code, he is the expert here :+) If there was anyone's style to follow, you would do well to follow his :+)

Although we have 2 different behaviors here: one fires all the bullets, the other allows for a single shot.

There is another exercise, modify the Fire function to take an argument for the number of bullets to fire in one burst. You could have a default argument, so it fires one with no argument, or the number specified in the argument otherwise.

Do the same to the Load function, allow a variable number of bullets to load.

While you are at it, implement the restriction that mbozzi had, say 15 bullets.

What is another way of writing this?

m_bullets -= 1;

Hint there is another operator.

Line 21 is a little superfluous, if it has run out, there are 0 left.

Lines 31-32 how many pistols have you created there?

You are using a magic number of 10. Consider making that a constexpr variable.

How many time does the for loop on line 34 run ?

There will be more things on the TODO list soon, lets get this part right first. :+)


> Ah, it should be unsigned, since you cant have a negative amount of ammo, only 0.

Using an unsigned integral type in the interface (for a value that should never be negative) does look attractive.
But it has a serious drawback: it removes our ability to detect a programming error: the violation of an invariant.

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>

void foo( int num_bullets ) // invariant: non-negative num_bullets
{
    std::cout << "foo called with num_bullets == " << num_bullets << '\n' ;
    
    if( num_bullets < 0 )
    {
        // handle error.
        // correct it to a valid value (say, zero) or throw std::invalid_argument
    }

    // rest of function
}

void bar( unsigned int num_bullets ) // no invariant: num_bullets will always be interpreted as non-negative
{
    std::cout << "bar called with num_bullets == " << num_bullets << '\n' ;

    // no point checking num_bullets, it will never be interpreted as negative
    // rest of function
}

int main()
{
    // say, by mistake (carelessness, computational error) the user passes a negative value

    foo( -123 ) ; // checked, caught, and handled

    bar( -123 ) ; // we won't even get a warning for this;
                  // the function would simply interpret the bits that form the value -235
                  // as a (typically huge) positive number
}


A robust interface should, to the extent possible, be able to detect and handle situations in which a programmer may (inadvertently) use the interface incorrectly. In short, using an unsigned type in the interface deliberately sacrifices the ability to detect that that a negative value was wrongly passed.
Ok. Can you tell us what the 4 different unsigned types are? Which would you choose, and why?


Without looking it up, I couldn't tell you technical details but I know of long, short, unsigned and signed and I know unsigned means the number cannot have a negative number in it, signed CAN have a negative number in it. Short I believe means it only allows a small number as int can be fairly large, long means it can allow a really big number. Some of these can be used together:

short unsigned int
long signed int


In the Fire function, it's first action is to fire, without checking if there are any bullets.

You could modify JLBorges code, he is the expert here :+) If there was anyone's style to follow, you would do well to follow his :+)

There is another exercise, modify the Fire function to take an argument for the number of bullets to fire in one burst. You could have a default argument, so it fires one with no argument, or the number specified in the argument otherwise.

Do the same to the Load function, allow a variable number of bullets to load.

While you are at it, implement the restriction that mbozzi had, say 15 bullets.


Ok, ill make those modifications.


What is another way of writing this?


ah, m_bullets--;

ok i'll work on this.
As always awesome advice JLBorges :+) I see what you are saying, and will keep that in mind.

However, what if there is an upper bound, or a range? Then we can enforce the precondition / invariant? There are many things which are bounded.




Re. the use of unsigned vs. signed: Good example from Chandler Carruth at 39:19 in this video:
https://www.youtube.com/watch?v=yG1OZ69H_-o

His talk is really about design-by-contract in the frame of undefined behavior - he's a compiler author. I would recommend watching the whole thing if you have an hour.

To clarify, the point of the example isn't really in favor of signed int but in favor of the implied narrow contract (that the argument must be positive), this time for efficiency reasons. He's talking about what @JLBorges noticed.
Last edited on
Without looking it up, I couldn't tell you technical details ...


Ok no worries. Looking up technical details is a good thing, it helps.

For integral types there are short, int, long and long long and all the character types. On 64 bit systems (Which is hopefully what everyone has now) there is no long long the long is already 64 bit.

Then there are modifiers like signed and unsigned the signed one is implicit, so one can leave it out.

There is another type : std::size_t this is the largest unsigned type the system has. It is used in the standard library for anything that needs, has or returns a size.

It is common to see this from beginners:

int MyVectorSize = MyVector.size();

There the compiler has to do an implicit conversion from std::size_t (that is what size() returns) to int, so it is better to write this:

std::size_t MyVectorSize = MyVector.size();

or

auto MyVectorSize = MyVector.size(); which decides the type automatically.

So one should use std::size_t when referring to subscripts in arrays or vectors etc.

Do go and look up what the maximum values of these types are. It's important to know about the characteristics of the types one is using.

Also be aware that there are many traps when mixing signed and unsigned types. Can you think of what some of them might be ?

Edit: Think about the simple functions add and subtract.
Last edited on
Ok so I got this, its not counting down all the way though, not sure what the problem is but this is what i figured out to make it fire full auto or semi-auto.

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 <iostream>


class Pistol
{
    public:
        Pistol() = default;
        explicit Pistol(int bulletCap) : m_bulletCapacity(bulletCap){}

        void Fire(bool m_fullAuto)
        {
            if(m_bulletCapacity > 0)
            {
                if(m_fullAuto == false)
                {
                    std::cout << "Full Auto Off\n";
                    for(int i = 0; i < m_bulletCapacity; i++)
                    {
                        std::cout << "Bang\n";
                        m_bulletCapacity--;
                        std::cout << "The weapon has " << m_bulletCapacity << " rounds left.\n";
                    }
                }
                if(m_fullAuto == true)
                {
                    std::cout << "Full Auto On\n";
                    for(int i = 0; i < m_bulletCapacity; i++)
                    {
                        std::cout << "Bang\n";
                        m_bulletCapacity--;
                        std::cout << "The weapon has " << m_bulletCapacity << " rounds left.\n";
                    }
                }
            }

            if(m_bulletCapacity == 0)
            {
                std::cout << "The weapon has run out of ammunition\n";
            }
        }

    private:
        std::size_t m_bulletCapacity = 3;
};

int main()
{
    Pistol pistol(15);

    pistol.Fire(true);

    return 0;
}
Ok.

Could you split the code up into Pistol.hpp and Pistol.cpp and main.cpp files, it will make it easier, this is going to get more complicated soon. I am sure you have split code into several files before.

With the constructor, you haven't checked the bounds of the argument - is it between 0 and 15? You have mbozzi's example to look at. In main you still have the magic number 15, make that a constexpr variable, and use it on line 48.

Why am I asking you to check if the values are between some bound or range? I am sure the others will generally agree, roughly 50% of code is checking whether data is OK, the other 50% is doing the actual calculation / processing. This is because we do our best to make sure programs don't crash. At the pointy end, people could die - think of that next time you are on a flight somewhere, or that people would rather have the electricity being on all the time.

With types in your class, you are mixing them std::size_t and int. The compiler is doing the implicit conversion, which I mentioned so that you wouldn't code that way. Seen as you haven't done the bounds checking, your code should be like JLBorges. Maybe it would be a good idea to start with JLBorges code, then modify it. Stick with his style of doing things.

I also said to do the same thing with the load function, which is also what JLBorges had.

The Fire function does not allow the user to specify the number of rounds to fire in one burst, and both branches of the if statement are almost exactly the same. You have jumped to full auto / semi automatic, I never said anything about that, not yet anyway.

Maybe enthusiasm has taken over, but you need to be more careful about following instructions. Read carefully what we are saying to you, don't rush into things. We are doing our best to help you step by step.
Maybe enthusiasm has taken over, but you need to be more careful about following instructions. Read carefully what we are saying to you, don't rush into things. We are doing our best to help you step by step.


Yeah sorry about that I do get a little over enthusiastic, also with all the other posts it gets a little confusing. I'll pick this up Saturday morning when I get home from work and see if I cant figure this out, I have the weekend off.
Last edited on
... also with all the other posts it gets a little confusing.


Just concentrate on the instructions given, refer to another post where it is mentioned.

On Sunday I am flying back to work interstate, and do quite long days, so I won't have much if any time to do detailed replies. There are plenty of other people to help though.

However, in my next post I will lay out my ideas for some exercises to do. The idea is to do a few simple things each time, so gradually the project will get more complex. There will be some side questions, to test whether you understand some basic but important concepts in C++.

Here are some concepts about what I am expecting for the way you to go about doing this project:

* Keep it simple
* Follow the instructions
* Do 1 exercise at a time, get that working and complete with all the features and style requirements I am mentioning here. Only then move onto the next exercise.
* When starting with a new exercise, start with the code you had in the previous exercise. Save each exercise in a different folder on your computer. There are going to be multiple *.hpp and *.cpp files (multiple classes), so it will be best to copy the whole folder to help avoid confusion. In your IDE, each exercise will be a separate project. Find out how to import existing files into the new project.
* Think about the code you are writing, be self critical of it. Don't spend 3 minutes writing the code, then post it. Instead review each line of code (LOC), see if you can see any potential problems with it. Are you mixing types? Is the LOC going to work for different values, are there any values which might cause it to fail? Remember the goal of always striving to write code that doesn't fail.
* There is going to be some planning / design required. That is, you might need to write down ideas, draw pictures to organise the design of the code, before writing any code.
* when writing the code, write comments about what the code is going to do. Then go back and write the actual code for each comment. Leave the comments in the code as a bit of documentation. This is going to help you organise your thoughts. I might ask to see all the comments first, before you write any actual code.
* For each function, write 3 lines of comments about what the function does
* For each function argument, write a comment about what the valid range of values is.
* This project is about real life objects (A pistol, so far) , think about what happens in real life, make you code work that way. That is, actions one would carry out in real life become functions in the code. As the project moves forward, there will be more objects and classes.
* Choose meaningful names for variables and functions. Good code should be mainly self documenting on the names you choose, it should tell a story of what is happening, just by the names of the variables and functions.
* Functions should only do one conceptual thing, and they should be relatively short - at the most say 30 LOC.
* There will be times where you need to do your own research. Even though we are helping you in detail, you should be able to go and look things up yourself. We don't want to write a brand new wiki with everything there is to know about C++.
* As the project gets more complex, hopefully it is instructive to see how the code changes. This project is different to a real life project in that we are starting very small (1 class) then gradually adding stuff. In real projects, modules worth of things are designed and coded all at once, so there is much less change in the code.

I will do another post soon with the exercises in it :+)
Pages: 1234