Classes and objects
Object-oriented programming is a way to structure a program by combining the algorithms and the data that the algorithms operate on into single entities called objects. Most object-oriented languages, including C++, are class-based. A class is a definition of an object—it describes the algorithms and the data, its format, and its relations to other classes. An object is a concrete instantiation of a class, that is, a variable. An object has an address, which is a location in memory. A class is a user-defined type. In general, any number of objects can be instantiated from the definition provided by the class (some classes limit the number of objects that can be created, but this is an exception, not the norm).
In C++, the data contained in a class is organized as a collection of data members, or variables, of different types. The algorithms are implemented as functions—the methods of the class. While there’s no language requirement that the data members of a class should be somehow relevant to the implementation of its methods, it’s one of the signs of good design when the data is well encapsulated in the classes, and the methods have limited interaction with external data.
This concept of encapsulation is central to the classes in C++—the language allows us to control which data members and methods are public—visible outside of the class, and which are internal—private to the class. A well-designed class has mostly, or only, private data members, and the only public methods are those needed to express the public interface of the class—in other words, what the class does. This public interface is like a contract—the class designer promises that this class provides certain features and operations. The private data and methods of the class are part of the implementation, and they can be changed as long as the public interface, the contract we’ve committed to, remains valid. For example, the following class represents a rational number and supports the increment operation, as exposed by its public interface:
class Rational { public: Rational& operator+=(const Rational& rhs); };
A well-designed class doesn’t expose any more of the implementation details than it has to through its public interface. The implementation isn’t part of the contract, although the documented interface may impose some restrictions on it. For example, if we promise that all rational numbers don’t contain any common multipliers in the numerator and denomination, the addition should include the step of canceling them. That would be a good use of a private member function—the implementation of several other operations will need to call it, but the client of the class never needs to call it because every rational number is already reduced to its lowest terms before it’s exposed to the callers:
class Rational { public: Rational& operator+=(const Rational& rhs); private: long n_; // numerator long d_; // denominator void reduce(); }; Rational& Rational::operator+=(const Rational& rhs) { n_ = n_*rhs.d_ + rhs.n_*d_; d_ = d_*rhs.d_; reduce(); return *this; } Rational a, b; a += b;
The class methods have special access to the data members—they can access the private data of the class. Note the distinction between the class and the object here—operator+=()
is a method of the Rational
class and is invoked on the object, a
. However, it has access to the private data of the b
object as well, because a
and b
are objects of the same class. If a member function references a class member by name without any additional qualifiers, then it’s accessing a member of the same class it’s invoked on (we can make it explicit by writing this->n_
and this->d_
). Accessing members of another object of the same class requires a pointer or a reference to that object, but is otherwise not restricted, as would have been the case if we tried to access a private data member from a non-member function.
By the way, C++ also supports C-style structs. But in C++, a struct isn’t limited to just an aggregate of data members—it can have methods, public and private access modifiers, and anything else classes have. From a language point of view, the only difference between a class and a struct is the default access—in a class, all members and methods are private by default, while in a struct they’re public. Beyond that, the use of structs instead of classes is a matter of convention—traditionally, structs are used for C-style structs (structs that would be legal in C) as well as almost C-style structs, for example, a struct with only a constructor added. Of course, this boundary isn’t precise and is a matter of coding styles and practices in each project or team.
In addition to the methods and data members we’ve seen, C++ also supports static data and methods. A static method is very similar to a regular non-member function—it isn’t invoked on any particular object, and the only way it can get access to an object of any type is through its arguments. However, unlike a non-member function, a static method retains its privileged access to the private data of the class.
Classes by themselves are a useful way to group together (encapsulate) the algorithms and the data they operate on and to limit access to some data. However, the most powerful object-oriented features of C++ are inheritance and the resulting class hierarchies.