November 13, 2023

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

Let's learn C++! Happy Birthday Ingrid!!

I've done object oriented programming in Java, Python, and Ruby. You would think I would be well equiped to do it in C++ with basic concepts well understood, but just some new syntax to learn.

Somehow C++ manages to make it different and complex. Supposedly the OO syntax used in C++ was inspired by Simula 67, whatever the heck that is. We can pin the blame there. The C++ class scheme began as a generalization of the C "struct".

At any rate, I don't intend to talk about basic OO (object oriented) concepts here. If you need that you will need to investigate other resources. I am just talking about how it is done in C++ for a person (such as myself) who is comfortable with OO programming in other languages.

Classes and objects

The big thing that C++ adds to C is classes. I'll give you two ways to think about a class: What a class adds to a C struct is that a class can contain functions as well as data. Some writers call functions that are contained in a class "methods" and I may have picked up this habit. So a struct gathers up a bunch of data items into a tidy package. A class gathers up data, along with functions to work on that data, into a handy tidy package.

I won't try to explain why this is wonderful or even to convince you that it is wonderful.

Our first class

I encourage you to get a C++ compiler running on your system and play around a bit with these extremely simple examples I am presenting. For me, on my linux system, this was as simple as typing "g++" on the command line, but you might need to install some packages. To compile a program you type:
g++ -o kat kat.cxx
In other words, exactly the same as you would invoke "cc", but now you invoke "g++".

I just spent a few minutes working up an actual program with a C++ class. The process itself is instructive and, once again, I recommend attempting even simple things yourself as an aid to learning. Here it is. Notice that this is just another hello world program, but was more or less the simplest thing I know how to throw together that shows a class in action. I will follow the code with a lot of explaining.

#include <cstdio>
#include <string>

using namespace std;

class Klass
{
    public:
	Klass ( void )
	{
	    _message = "Hello World";
	}
	void greet ( void )
	{
	    printf ( "%s\n", _message.c_str() );
	}
    private:
	string _message;
};

int
main ()
{
    Klass first  = Klass ();
    first.greet ();

}
There you go. If you compile and run this, it will print hello world. I'll take this line by line then perhaps go back with some overall comments.

First we include a couple of standard C++ headers to get the string class as well as printf(). The namespace declaration tells the C++ compiler to expect the std:: prefix on all methods (functions) we call. Without this, we would need to write std::printf and std::string and so forth.

Then we define a class. How exciting! It has public and private sections, where the private stuff is for internal use only. There could also be private functions (methods), but we don't have any here. We have a string called _message. The convention of starting with an underscore for all data components of a class is optional, but I like it.

The first method in the class "Klass" is called "Klass". When a method has the same name as the class containing it, it is a constructor, and its job is to initialize an object generated from (the OO people like to say "instantiated from") the class.

Now look at main. It defines a variable "first" of type Klass. This will be a reference to an object of the type Klass. Then we call "Klass()" to generate an object. This is exciting, we just generated (instantiated) our first object. We could call this over and over to generate lots of objects, but I can't see that that would serve any purpose for this particular class.

So we get one shiny new object, and it will hold our message. The message gets initialized by the constructor and we are good to go. Then we see this:

first.greet ();
This is a pattern you should get used to. We have an object (actually an object reference) and we want to call a function (method) that is part of this object, and this is how we do it. This will print the internally stored greeting message.

It is illegal to use first._message in any way. It is private and not visible outside of the class. If we moved it into the public section we could directly access it, but we have to ask if that is really a good design decision.

Also, a fine point. Notice this line:

printf ( "%s\n", _message.c_str() );
Since we are using the fancy C++ string class, we can't just use the %s syntax in printf to print it. As it turns out _message is an object from the string class, and the string class provides a handy method called c_str() to convert a string object to a good old fashioned array of character style C string -- and printf likes that. The syntax with an object name, then a ".", then a method name should be familiar from our use of the greet() method.

I will also point out that this dot syntax is exactly what is used for C struct elements. But with classes we also use it to invoke a function in the class.
Let's add two lines to main() ---

main ()
{
    Klass first  = Klass ();
    first.greet ();

    Klass *p = &first;
    p->greet ();
}
We declare a pointer to a class of type Klass and initialize it. Then we use the familiar C pointer syntax "->" to invoke the greet() method, which prints a second message. Just like our old friend, the C struct.

Note that C++ lets us put the declaration "Klass *p" right in the middle of the code, rather than forcing us to sequester it at the start of the main() function as we would have to do with C.

Destructors

It seems so negative and sad to talk about these, but they are part of the language so we or forced to do so. Truth be said, sometimes things have outlived their usefulness and are just so much annoying clutter. A destructor is just another method, but with the name of the class, prefixed by a tilde, so in our case:
~Klass ( void )
{
    // do destructive stuff
}
As to how and why destructors get used, I am not smart enough to say. They are optional, and for the sort of embedded programming I do, things rarely get destroyed, so enough said.

Let's overload our constructor

We talked about this already in our "basics" section, but it is possible to have functions with the same names and different arguments. This might be done like this:
#include <cstdio>
#include <string>

using namespace std;

class Klass
{
    public:
	Klass ( void )
	{
	    _message = "Hello World";
	}
	Klass ( string xyz )
	{
	    _message = xyz;
	}
	void greet ( void )
	{
	    printf ( "%s\n", _message.c_str() );
	}
    private:
	string _message;
};

int
main ()
{
    Klass first  = Klass ();
    first.greet ();

    Klass next  = Klass ( "Hello Charley" );
    next.greet ();
}
You should get the idea. Now we have two objects of type Klass with different strings hidden inside.

Inheritance

Some people seem to think that this is the end-all an be-all of OO programming. I think it is cute, but almost never use it. I have written large programs in both Java and Python with many classes and never even felt an itch to use inheritance. But, let's explain it, so you can look down your nose at it like I do.
Here is a complete example:
#include <cstdio>
#include <string>

using namespace std;

class Klass
{
    public:
        Klass ( void )
        {
            _message = "Hello World";
        }
        Klass ( string xyz )
        {
            _message = xyz;
        }
        void greet ( void )
        {
            printf ( "%s\n", _message.c_str() );
        }
    protected:
        string _message;
};

class Zap : public Klass
{
    public:
        void set ( string xyz )
        {
            _message = xyz;
        }
};

int
main ()
{
    Klass first;
    first.greet ();

    Zap z;
    z.set ( "No clams here" );
    z.greet ();
}
There are a number of interesting things introduced here. First notice in main that I simply declare a class variable and I get a working object without needing to explicitly call the constructor. Just declaring "Klass first;" gives me an initialized object. This is a surprise to me, but it works. I'll note also that unlike other languages there is no use of the word "new" to generate new objects.

Expanding on this discovery just a bit, if we want to instantiate a new object and use the second constructor to specify the initial string, we could write this:

Klass k ( "Baloney" );

But let's not get entirely sidetracked from inheritance. The key line is this one:

class Zap : public Klass
This says that we are defining a new class "Zap" that inherits from class "Klass". The lingo used is that "Zap" is a derived class and "Klass" is the parent class. There are rules and details that I won't get into (largely because I don't understand them yet).

Also note that in the parent class "Klass", we have changed "private" to "protected". This is because private things are just that, private to the parent class and are not inherited. The word "protected" means that they are available to derived classes, but not to the world at large. There is another way to make private things available to other classes which C++ calls making the class a "friend", but I don't know how that works just yet.

And there you have it. The purpose of this sort of thing is to take an existing class and add some functionality in the derived class. Often the parent class is a general collection of things and derived classes make a more specific class. So you might have a general "auto" class and specific "ford" and "chevy" classes, to use a silly example.

I'll note also the interesting discovery that we don't need to call constructors explicitly. In good old "papoon", I had been puzzled by the line:

UsbDevCdcAcm    usb_dev;
But now I understand. You just declare the type and voila.

However, we haven't entirely escaped new, we could write this:

Zap* z = new Zap;
The "new" here is nothing like other languages, it is just the built in allocator that is part of C++ that could be used for arrays or variables, not just classes and objects. What happens here is that a new object gets created and we get a pointer to it. We will need to access methods using "->" and not a dot, since we have a pointer.

inline methods - and good practices

Just like in modern C compilers you can declare functions (and methods) as inline. The compiler will (if possible and if it feels like it) put their code inline at each invocation. This is just a hint and can be ignored. It yields faster, but bigger code.

Class methods are inline without being declare so if they are defined in the class statement. We haven't described any other way to do it, so this may seem simply interesting. It is possible to define methods outside of a class statement, but they will still need to be declared in the class statement. All of this won't make any sense unless we give an example.

#include <cstdio>
#include <string>

using namespace std;

class Cod
{
    public:
	Cod ( string xyz = "nobody home" )
	{
	    _message = xyz;
	}
	void greet ( void );
    private:
	string _message;
};

void Cod::greet ( void )
{
	printf ( "%s\n", _message.c_str() );
}

int
main ()
{
    Cod c ( "hello out there" );
    c.greet ();
}
Here we declare the greet() method inside the class, but define it outside. This can be important if we have the definition in a header file to be shared by multiple other files, but want to put the definition in a single file. Just the sort of thing we ought to do if we are writing bigger programs.

Note that the method name is "fully declared" with the class name prior to a pair of colons, i.e. "Cod::greet". This is just how you do things.

Just for fun I played with the constructor a bit, giving it a default value. This is an alternative to what I did previously where I wrote a pair of constructors (in that case using overloading to accomplish the same thing).

When a method is done outside the class like this, it won't be inline by default, although you are free to declare it so.

const, mutable, and this

Sort of a random collection of interesting things. I should say that I am skipping over lots of "fine points" in the C++ language. How can I not do so? People write 1000+ page books and people beg for more, get confused, run away beating their head with their fists.

You can mark class methods as "const" and this indicates that they do not (or are not intended to) change data members of the class. You can also mark data members as "mutable" to indicate that you don't particularly care if they get changed. The syntax is:

	void greet ( void ) const;
That's right, the "const" gets slapped onto the end.

If you have done OO programming in Python you have seen "this" endlessly. This is because (and I don't mind saying so) the design of Python is stupid. It is stupid in this particular area. In others too, but don't be me started.

In C++ you rarely need to use "this" but it refers to the current object. I see "this" used a lot in papoon regbits, so we best be ready.

Long enough

There is plenty more, but this page is long enough, so go to the next page.
Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org