The Wall of Abstraction
A class is an interface paired with an implementation, similar to the Time structure
you created in the lesson on Information Hiding. This is called the wall of abstraction, illustrated by the comic below.The public interface specifies how clients interact with objects, and the private implementation specifies how the functions in the public interface are implemented.
The Time struct (even when paired with an interface, so the data is hidden), still allows users to directly manipulate its data members. Classes take a different approach; with classes, the data inside your objects will only be accessible by the member functions, forcing the client to access and modify data in a safe way.
Class Definition Syntax
The Time Class Definition
class Time
{
public:
int hours() const;
int minutes() const;
Time sum(const Time& rhs) const;
Time difference(const Time& rhs) const;
istream& read(istream& in);
ostream& write(ostream& out) const;
private:
int m_hours;
int m_minutes;
};
To create a class definition for Time, similar to the structure from last week, follow these rules:
- Instead of struct, use the class keyword. There is no public in front of this as with Java.
- The public keyword, followed by a colon, indicates the start of the public interface. Here we prototype the member functions it contains.
- The member functions hours(), minutes(), sum(), difference() and write(), all access the hours and minutes data members without changing them. When this is the case, add the const keyword after the argument list. We say these functions are accessors.
- The read() member function does modify the Time object. This is called a mutator.
- The class definition ends with a semicolon, just like a structure. This is not optional.
Data Members
Most of the implementation will appear inside a .cpp file. Defining the data members which store object state, is written inside the header file instead. A common practice is to use a special indicator like m_ to show that it is a data member.
The Time struct used two individual data members: one for hours and one for minutes. This is fine; it allows you to store all of information needed. By adding private, you can prevent clients of Time from accessing the data members directly.
Public and Private
So, what do public and private mean in C++? If a member of a class is public, then any part of your code can access and manipulate it directly. If you have a public member function, any code can call it using an object of that type. If a data member is marked private, then only member functions of the class can access it.
The public and private keywords are the C++ mechanism for defining interfaces and enforcing encapsulation. Once you add private, the compiler enforces the appropriate encapsulation.
By prohibiting clients from directly accessing private data, the implementation can assume that all access to that data goes through the public interface (unlike the Time struct of last week, where clients should use the member functions, but were not prohibited from directly accessing the data members m_hours and m_minutes.)
Actually, the only real difference between class and struct in C++ is that with a struct, the members are public by default; with a class they are private. By convention, we will use struct for POD (plain-old-data) data types, and class for encapsulated types.
The Implicit Parameter
Consider the implementation of the hours() member function of the Time class shown here:
int Time::hours() const
{
return m_hours;
}
int main()
{
Time t; // a Time object
cout << t.hours() << endl; // value of t::m_hours
}
The hours() member function does not contain a local variable named m_hours. But, the function still compiles and runs correctly. Why?
In a member function, you may directly access and manipulate any or all of the class's data members by referring to them by name. You don't need to indicate that m_hours is a data member, nor do you specify which Time object it belongs to.
C++ assumes that all data members are the data members of the receiver object, and so the line return m_hours means “return the value of the m_hours data member of the object on which this function was invoked.” In such a case, the receiver object is known as the implicit parameter, passed to every member function.
The Pointer this
Behind the scenes, the implicit parameter is a pointer to the calling object. Every member function has an implicit parameter. Thus the effective signature for the hours() function is as if you had declared it like this:
int hours(const Time* const this);
The keyword this is the name which is automatically supplied for the implicit parameter. The const following the Time* means that the value inside the pointer can never be changed; it always points to the block of data containing the object's data members. The const following the member function header means that the implicit parameter is a pointer to a const Time object.
If you wish, you can explicitly use the pointer when calling other member functions, or accessing data members:
int Time::hours() const
{
return this->m_hours;
};
Initializing this
When you call a member function like this:
Time t; // a Time object
cout << t.hours() << endl; // value of t::m_hours
That call is implicitly translated into code that acts as if you had written:
Time t; // a Time object
cout << hours(&t) << endl; // value of t::m_hours
Because of this call, the this pointer is initialized to the address of the calling object.
The sum Member Function
Let's examine the behavior of this and const a little more closely, by considering the sum() member function from Time:
class Time
{
public:
Time sum(const Time& rhs) const;
. . .
};
When you add two Time objects (a + b) together like this:
Time after = a.sum(b);
The caller (the implicit parameter) is the left-hand-side of the expression a + b. Thus, the effective implicit prototype for the function is similar to this:
Time sum(const Time* lhs, const Time& rhs);
In the implementation, however, instead of the explicit lhs parameter shown here, you'd use the keyword this to access the data members.
Time Time::sum(const Time& rhs) const
{
auto tMinutes = this->m_hours * 60 + this->m_minutes;
auto dMinutes = rhs.m_hours * 60 + rhs.m_minutes;
. . .
}
If you leave off the keyword this, it is assumed. Notice that when you implement a const member function, you repeat the word const in the implementation.