November 14, 2023

Let's learn USB! -- C++ classes, part 2

The "part 1" page on classes was getting plenty long enough, so I grabbed a new sheet of paper and started this.

Static class members

Thus far, the data members of our class belong to each object that gets instantiated. In other words, each objects gets its very own data, not shared by other objects of the same class and potentially different.

Sometimes it is useful and important to have variables that belong to the class and are shared by all objects. For example, what if we wanted to have a count of how many objects had been instantiated. (I feel bold and strong when I use big words like "instantiated" -- and they are the official words, so get with it, "man up" -- I can hear you whining about all this new language).

It is easy. Put the word "static" in front of the variable and you are done.

class Zombie
{
    ...
    private:
	static int count = 0;
};
Note that we can provide an initial value.

We can also have static methods! When we use "normal" methods (i.e. "instance methods") we have to prefix them with an object as in z.greet(). To call a static method, we prefix it by the class name using this syntax:

    x = Klass::get_count();
When will you need such things. You will know.

Overloading operators

It is worth knowing that you can define your own functions to handle ++, =, !=, ==, + and so forth. I was going to leave it at that, but then I got tempted to write an example and learn more.
Here you go:
#include <cstdio>
#include <string>

using namespace std;

class Clam
{
    public:
	Clam ( int xyz = 999 )
	{
	    _value = xyz;
	}
	void show ( void );
	void set ( int );
	Clam operator+ ( const Clam& );
    private:
	int _value;
};

inline void Clam::show ( void )
{
	printf ( "%d\n", _value );
}

inline void Clam::set ( int xyz )
{
	_value = xyz;
}

inline Clam Clam::operator+ ( const Clam &arg )
{
	return Clam ( _value + arg._value );
}

int
main ()
{
    Clam c ( 42 );
    c.show ();

    Clam d = c;
    d.set ( 13 );
    d.show ();

    Clam x = c + d;
    x.show ();
}
Once again, this is a complete program, fully tested, that you can copy and run yourself.

The key thing to observe here is the "operator+" business which introduces the overloading of the infix "+" operator for addition. The "Clam" class here is just for demonstration and is nothing more than a container for a single integer. I was somewhat surprised that I get assignment of two class objects for free! This is not always the case. If your class contains some kind of storage allocated using "new" you will need to provide your own special "Clam" constructor that handles construction given an object as an argument.

We add the two objects "c" and "d" to get "x" and the show gives us the value 55. Voila!

Friends

The idea here is that one class may have private stuff, but would like to share it with some other class (or at least certain methods in another class). Here is an example, building on a stripped down version of the "Clam" class from above:
#include <cstdio>
#include <string>

using namespace std;

class Clam;

class Grab
{
    public:
	Grab ( int xyz = 999 )
	{
	    _info = xyz;
	}
	void show ( void );
	void gimmee ( const Clam& );
    private:
	int _info;
};

class Clam
{
    public:
	Clam ( int xyz = 999 )
	{
	    _value = xyz;
	}
	void show ( void );
	friend void Grab::gimmee ( const Clam& );
    private:
	int _value;
};

inline void Clam::show ( void )
{
	printf ( "%d\n", _value );
}

inline void Grab::show ( void )
{
	printf ( "%d\n", _info );
}

inline void Grab::gimmee ( const Clam & xx )
{
	_info = xx._value;
}

int
main ()
{
    Clam c ( 42 );
    c.show ();

    Grab g ( 123 );
    g.gimmee ( c );
    g.show ();
}
I try to keep it simple. Honestly I do. There is really only one line here that is exciting, with the word "friend". Here is the idea. The "Grab" class is pretty much exactly the same as the "Clam" class -- just a container for a single integer for purposes of illustration. But I want to add a "gimmee" method to the "Grab" class that will extract the integer value from an object of the "Clam" class. Since "_value" is private, that would ordinarily be illegal. But by declaring Grab::gimmee a friend, we give it access to private stuff.

A couple of things are worth noting. One is the idiom of passing objects by reference and declaring them "const". This lets the compiler know it can avoid copying the whole object and seems to be a common and good practice (not necessary mind you, but good).

The other thing to note is the line:

class Clam;
We have a sort of chicken and egg problem with the two classes needing to refer to each other. Each one needs to come before the other. This solves the problem and makes the compiler happy.

Parting words

We are not done, there is still templates in the last page. But we are done with classes, at least with what I intend to try to explain about classes. There is certainly more and lots of subtle issues to learn about if you choose to continue your education. This is enough to get me started and you now have almost everything you need to wrap your head around C++ classes.
Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org