2 Questions

Started by
7 comments, last by Alberth 8 years, 7 months ago

I got a few questions:

How can I call and do something on all objects of a class?

Something like:


MyClass var1;
MyClass var2;
MyClass var3;

I have var1, var2, and var3, how can I be able to call all of those variables/objects with just 1 function. Like:

-- Pick all objects of MyClass(such as var1, var2 and var3)

-- Do something with them

And last question, about the "this" keyword.

If I do something like this:


MyClass::DoSomething()
{
    something = this;
}

Then if I use it like:


obj.DoSomething()

Then the object/variable "obj" will be the value of the "this" keyword in the MyClass::DoSomething() ?

Thanks in advance...

Advertisement

If you need many you don't put numbered single variables, you use an array/vector.


std::vector<MyClass> myobjects;
myobjects.resize(3);
// ...
for(MyClass& x:myobjects) {
    x.DoSomething();
}

this is just a pointer to the object when you are within a method.

Sometimes it is easier to answer these questions with a concrete example of what the objects involved are and the function you're trying to call.

You cannot automatically apply a function to all objects of MyClass (unless you do work for it, but that is more advanced).

The system does not keep track of where the objects are, you have to point out what you want to call yourself.

The simplest form of calling a function on them is to list all calls explicitly:


var1.doSomething();
var2.doSomething();
var3.doSomething();

If you don't make separate variables, but add the objects to a container like an array, you can loop over them:


MyClass objects[5];  // Make 5 'MyClass' objects.
for (int i = 0; i < 5; i++) {
    objects[i].doSomething(); // Call the function on the i'th object.
}

(object is the official name for the data attached to variable like your "var1").

Arrays are very static, above you have 5 objects, always. There are other forms (std::list, std::vector), but they come with new challenges to consider.

As for 'this', consider the following class + code:


class AB {
public:
    int value;
    AB() { value = 4; };

    void g1(int val) {   // Set the 'value', simple version.
        value = val;
    }

    void g2(int val) {   // Set the 'value', what is really happening.
        this->value = val;
    }

    void g3(int value) {     // Set the 'value', confusing version.
        value = value;       // FAIL: it assigns the parameter and not the class member.
        this->value = value; // Works.
    }
};

AB v;  // Create an object named 'v' of the class.
AB w;  // Create an object named 'w' of the class.
v.g1(5); // Call 'g1' of 'v'
w.g1(6); // Call 'g1' of 'w'

Function g1 set the value of a variable of the object that you call. With "v.g1(5)", variable "v.value" is changed, while with "w.g1(6)", variable "w.value" is changed. In particular, the second call should not change the value of "v.value".

Note that inside "g1", there is no "v", or "w" (they are defined later). You may even define them in another file, or use a different name. In the array example, there is only "objects" and that contains 5 objects rather than one!

So how does "value = val" in g1 know what to change?

Simple, besides the "v" and "w" name that you create, there is also a "this" name created by the compiler. "this" only exists inside a method in a class, and points to "myself". So for the g1 function of "v", it points to "v" inside the g1 function. For the g1 function of "w", it points to "w" inside the g1 function.

Function g2 shows what g1 really does. g2 does the same as g1, but explicitly refers to itself by "this->", saying "I am changing my own value here".

In this example, it is not needed g1 works just as fine. However there are cases where it is useful to be able to point to yourself, for example when an object passes itself as a parameter to a function. A contrived example is g3, where I 'accidently' added a "value" parameter to the function. Now if you do "value = value" here, it fails. It would be equal to "val = val" in the g1 function. (in the g1 case it's just a lot less confusing what happens smile.png ).

If you use "this->value = value" instead, you say "assign value (the parameter) to "my own" value (the value in the class).

Obviously, it's much less confusing if you use a different parameter name, but it's nice to know you can still access and change everything even if you pick unlucky names for variables and parameters.

Ok, but which is better to use, vector, map or list?

There is no single best (this holds for pretty much everything, if there is a single best, you would not have more than one option).

std::vector stores its elements consecutively. It takes minimal space, but taking any element out of it other than at the end is expensive (other elements have to move to fill the gap). Access to the n-th element is quick.

std::list stores elements in a double linked list (typically, check the details of your compiler), so it's cheap to add and remove, but finding the n-th element is a pain (you have to iterate to it.

std::map<K, V> is an associative array, that relates values of type K to values of type V, like a dictionary in Python. You can quickly find if a given value k of K is in the map, and if so, get its associated value v (of type V).

If you take "int" as type K, then you have again an array-like structure. Getting a value on the given index number is fast (slightly slower than vector), adding and removal is also fast (slightly slower than list), and you can have 'holes' in the map, eg store a value v for k = 1, 2, and 5.

It takes a lot of memory, internally it keeps a tree of nodes to be able to find arbitrary k values quikcly.

So which one is best depends on your intended use. You will find that holds in general, you typically design your data structures such that the common operations are quick. Depending on the intended use, you thus make different choices.

Ok so I tried making a Menu system, I decided to use std::map for it, but I keep getting this error:


error: cannot convert 'Game::MenuButton' to 'std::map<std::basic_string<char>, Game::MenuButton*>::mapped_type {aka Game::MenuButton*}' in assignment|

I don't know what I'm doing wrong. Here's my code:

menus.hpp:


#pragma once
#include <map>
#include <string>
namespace Game
{
    class MenuButton
    {
        float x, y, width, height;
        const char* state;
    public:
        MenuButton(float, float, float, float, const char*);
    };
    class MenuManager
    {
        std::map<std::string, Game::MenuButton*> MenuContainer;
    public:
        MenuManager();
        ~MenuManager();
        void addButton(std::string&, Game::MenuButton&);
        void removeButton(std::string&);
        void SetX();
    };
}

menus.cpp(Not the full code, just the part where the error happens):


void Game::MenuManager::addButton(std::string& name, Game::MenuButton& newButton)
{
    MenuContainer[name] = newButton; // <------ Error
}

Help please...

The error is because you mistakenly put a pointer * in the declaration of the map type although C++ containers are made for value types.

Indexing by string is also bad, because it is error prone and slow. The container class you should prefer is std::vector.

If you add parameter names to the methods declarations, it becomes easier to read


MenuButton(float x, float y, float width, float height, const char *state);

The constructor is a bit of a special case, since you typically copy the provided values into the object. Depending on your preferences you can do


MenuButton::MenuButton(float x, float y, float width, float height, const char *state)
    : x(x), y(y), width(width), height(height), state(state)
{ }

"x(x)" etc looks a bit confusing, and not everybody likes it. Alternatively, you can change the name of the parameters or the data members somewhat, below I modified the parameter names.


MenuButton::MenuButton(float x_, float y_, float width_, float height_, const char *state_)
: x(x_), y(y_), width(width_), height(height_), state(state_)
{ }

Another option is shown below. This form has a slightly different meaning though, which is especially relevant in derived classes and const data members.


MenuButton::MenuButton(float x, float y, float width, float height, const char *state)
{
    this->x = x; // Use "this->" prefix to denote you assign to the object data rather than the parameter.
    this->y = y;
    this->width = width;
    this->height = height;
    this->state = state;
}

As for strings:

As wintertime already said, strings are not nice to use, they are cumbersome to deal with (messy details with memory management), and typos in strings are easy to make, and take forever to find.

If you have a known fixed set of values, it's best to make it an enumeration:


enum ButtonState {
    BST_VISIBLE,
    BST_CLICKED,
    BST_DISABLED,
};

By convention, its values are all uppercase, as they are like constants. Also, many people add a prefix to make groups of such values unique.

You can use such a state by name of the enum, like


MenuButton::setState(ButtonState new_state) { ... }

The advantage is that the compiler will refuse to compile unless you give one of the enum values in the setState call. Typos will be caught by the compiler thus. Also, enum values are easily converted to integer numbers in case you need that.

This topic is closed to new replies.

Advertisement