As you have seen in 7.1, structs are a very nice way to
create new data types. And as you have seen in 7.1.2,
it is possible to write functions like read_wizard and
print_wizard to access them in a simple way.
Fortunately there is also another way to create new types in C++, which is much more powerful and simple: classes.
A class is declared just like a struct; thus, we could declare
Wizard as follows:
class Wizard {
string name;
int age;
};
Notice that the semicolon after the closing brace is, just like in a
struct, not an optional.
There is one big difference between the struct Wizard and the
class Wizard: the scope of the members. While in the struct --
which has so-called public scope -- one could access the
members using the dot operator (wizard.age, for example), this
is not possible in the class, which has private scope. In fact,
it is not possible to access the members at all from outside
the class.
The problem is that each member of a struct or a class has either public, private or protected scope. The public members can be accessed from anywhere. The private members can be accessed only from inside the class. The protected members are somehow in between, but we'll talk about them later; you don't need them yet.
By default (if you don't specify anything else) the members of a class
are private. If you wanted the class Wizard to be perfectly
analogous to the struct, you'd have to declare it like this:
class Wizard {
public: // Whatever follows this is public
string name;
int age;
};
If you wanted to declare one member private and one public, you could
do it like this:
class Wizard {
public:
string name;
private:
int age;
};
or, using the fact that the default is private, simply like this:
class Wizard {
int age;
public:
string name;
};
Let's go back to our good old Wizard now. Its original
declaration was
struct Wizard {
string name;
int age;
};
As it is a struct, all members are public. This means that every
programmer getting his hands on a Wizard object can change its
age and name as it likes. This is of course a Bad Thing. To avoid
that, we could declare Wizard as a class, like this:
class Wizard {
string name;
int age;
};
This way, all members being private, no function could mess with our
poor wizard. The problem is that no function could access any of its
data either; in fact, it wouldn't even be possible to give the name
and the age meaningful values.
What we need are some privileged functions which can access the wizards interior; we know those functions won't harm our wizard, and all the other programmers could use those functions to do whatever they need to do with the wizard.
That's exactly what member functions are. They are functions which
belong to the class, and can thus access all of its members --
public, private and protected -- without any restrictions. The
read_wizard function could be easily implemented as a member
function as follows (we call it only read because, being part
of Wizard, it is obvious that it refers to wizards and not to
witches):
class Wizard {
// The default is private, so we don't need to
// specify
string name;
int age;
public:
void read() {
cout << "Name: ";
cin >> name;
cout << "Age: ";
cin >> age;
}
};
Before you start wondering about why read does not specify
which wizard name and age refer to, look at how
member functions are called:
Wizard gandalf; gandalf.read();Now it should be clear. The members
name and age used in
read automatically refer to gandalf.name and
gandalf.age because read was called through
gandalf. If instead of gandalf.read() you had called
sauron.read() they would have referred to sauron.name
and sauron.age.
In 8.2.2 I pointed out why it is important to use a const
reference (instead of a non-const one) when a function does not modify
an argument. With member functions the same problem arises: some of
them, as read() may modify their object, others, as for example
print(), do not.
As we do not explicitly specify the reference to the object which is
modified, it is not possible to use the same method anymore. Instead
of that, the const keyword is appended after the closing
parenthesis of the function declaration, like this:
class SomeClass {
// ...
void this_function_does_not_modify_the_object() const {
// ...
}
void this_function_modifies_the_object() {
// ...
}
};
You cannot modify the members of an object in a const member function,
but they are the only type of member function you can call on a
constant object. If print is a const member function and
read isn't, you can call print on a constant object but
not read.
This means that if, for example, you have a function f taking a
const reference to a wizard, you can only call the const member
functions of that wizard inside the function, as follows:
#include <iostream> // for cin and cout
#include <string>
using namespace std;
class Wizard {
public:
void read() {
cout << "Name: ";
cin >> name;
cout << "Age: ";
cin >> age;
}
void print() const {
cout << "Name: " << name << "\nAge: " << age
<< '\n';
}
private:
string name;
int age;
};
void f(const Wizard& wizard) {
wizard.print(); // This is ok
// wizard.read(); // ERROR!
// You cannot modify wizard!
}
int main() {
Wizard some_wizard;
some_wizard.read();
f(some_wizard);
}
Obviously, it would not be possible to declare Wizard::read()
as const because it modifies the Wizard object. (Notice how I
wrote Wizard::read(). In C++, that's the way to refer to a
member function; it means ``the read() member function of the
Wizard class''). But it would be possible to define
Wizard::print() as non-const, even if it does not modify the
Wizard object. If you did so, you could not even print
a constant Wizard; in other words, even the line
wizard.print() in the example program above would be an error.
Declaring functions which do not modify the object as const is thus important; if you don't, you won't be able to use them in const objects. This is known as const correctness, and it is much more important than you might think right now.
The technique of declaring the member functions as public and the member variables as private, thus not allowing anybody to mess with them directly, is called encapsulation. Generally it is a sign of a good design; this does of course not mean that any public member variable is bad and should be wrapped into member functions. There are cases where that wouldn't make any sense.
Declaring member functions to access the content of a class has many advantages; one of them is that it is easy to change the way the data is actually stored inside the class; if you allow everyone to access the member variables directly, this is not possible any more. It may seem more work at the beginning, but trust me, in the end you'll be glad if you did so.
For example, imagine a word processor written in C++, which has class
Document storing, well, documents. Imagine that the
Document class has a member variable of type string
called text to store the text, and some other member variables
to store all the things which the text editor must know about the text
(like the author, the creation date, and so on). Suppose that the
Document class has also a member function called save().
Now, if you have not encapsulate the variables, everytime save()
is called you have to write the whole document to the hard disk, even
if it was not modified because you cannot easily know whether it was
changed or not. After all, any part of the outside code could have
changed something. (It would of course be possible to calculate some
kind of checksum everytime the document was saved, and recalculating
it when save() is called and comparing it to the previous
checksum, but that would just be overkill).
If you have encapsulated the variables, everything becomes much
easier. For example, you have a const member function called
get_author() to retrieve the author's name, and one (non-const,
of course) called set_author() to change it. All you have to
do now is adding a single variable of type bool to the class,
called changed. (A bool variable can only have two
values: true or false). Now, if set_author() (or
any other member function modifying the class) is called you set the
changed to true; so in save() you can easily
check whether the document was changed.
void Document::save() {
if (changed) { // equivalent to "if (changed == true)"
// save the document
// ...
changed = false;
}
}
Or, another more realistic example. Suppose you have written a very
complex algorithm to do special searches on a list of objects, which
is represented by an instance of the class List, which stores
all objects as an array accessible through List::objects. Your
algorithm will use statements such as some_list.objects[n] to
access the different objects. But as the number of objects grows
larger and larger, you realize that it is not possible to keep all of
them in memory at the same time. Thus you decide to change the
implementation of List so that it only keeps a small set of
objects in memory, and leaves the rest on disk, and automatically
loads the necessary objects if the algorith requests them. Now you
have a problem, because there is no way you can do that without
changing the interface of List. You'll have to adapt or at
least recompile the algorithm. If you had used proper encapsulation
and defined a function such as List::get_object(int index) to
retrieve the objects, you could just change its implementation without
touching the algorithm.
You might wonder now why I even bothered introducing structs, if
everything a struct can do can be achieved through a class with a
public: at the beginning.
In C++ the difference between a struct and a class is only the default scope of the members: public in structs and private in classes. They are interchangeable for all the rest: structs can have member functions and everything else a class can.
The programming language C had only structs and no classes. Those
structs did not allow member functions and scope declarations; they
were used only as described in 7.1. C++, which was initially
only an enhanced C, kept the structs and introduced the classes, which
were, at least at the beginning, not much different from the
structs. Classes evolved to what they are now, and, as it was easiest
for the compiler writers, struct became an alias for
class, with the default scope as only difference.
Of course you can do what you like, but I'd recommend you to follow the rest of the world and use structs when you use none of the features of C++ features (as scope, and member functions), and classes for everything else. In other words structs are only for plain variable groups with no member functions.