course of C++ programming language

lecture 4, 5: classes and objects


Class is a new type

The aim of the C++ class concept is to provide the programmer with a tool for creating new types that can be used as conveniently as the built-in types. A type is a concrete representation of a concept. For example, the C++ built-in type float with its operations +, -, *, etc., provides a concrete approximation of the mathematical concept of a real number. A class is a user-defined type. We design a new type to provide a definition of a concept that has no direct counterpart among the built-in types.

The fundamental idea in defining a new type is to separate the incidental details of the implementation (e.g., the layout of the data used to store an object of the type) from the properties essential to the correct use of it (e.g., the complete list of functions that can access the data).

Data members and function members

Variable names inside a struct do not clash with global variable names.

C++ functions can be placed inside structs as member functions. All the data members are exactly the same as before, but now the functions are inside the body of the struct.

In C++, instead of forcing you to pass the address of the structure as the first argument to all the functions that operate on that structure, the compiler secretly does this for you.

It's important to realize that the function code is effectively the same as it was with the C version of the library. The number of arguments is the same (even though you don't see the structure address being passed in, it's still there), and there's only one function body for each function.

struct Date { int d, m, y; void init (int dd, int mm, int yy); // initialize void add_year (int n); // add n years void add_month (int n); // add n months void add_day (int n); // add n days };

Because different structures can have member functions with the same name, we must specify the structure name when defining a member function.

void Date::init (int dd, int mm, int yy) { d = dd, m = mm, y = yy; };

In a member function, member names can be used without explicit reference to an object. In that case, the name refers to that member of the object for which the function was invoked.

Member functions can be invoked only for a specific variable using the standard syntax for structure member access.

Date today; today.init(23,3,2009);
Classes and objects

The construct

class X { // ... };

is called a class definition because it defines a new type. For historical reasons, a class definition is often referred to as a class declaration. Also, like declarations that are not definitions, a class definition can be replicated in different source files using #include without violating the one-definition rule.

X a, b, c;

An object is an instance of appropriate class. In C++, an object is just a variable, and the purest definition is "a region of storage" (this is a more specific way of saying, "an object must have a unique memory address"). It's a place where you can store data, and it's implied that there are also operations that can be performed on this data.

Access controll

The declaration of Date provides a set of functions for manipulating a Date. However, it does not specify that those functions should be the only ones to depend directly on Date's representation and the only ones to directly access objects of class Date. This restriction can be expressed by using a class instead of a struct.

class Date { int d, m, y; public: void init (int dd, int mm, int yy); // initialize void add_year (int n); // add n years void add_month (int n); // add n months void add_day (int n); // add n days };

The public label separates the class body into two parts. The names in the first, private, part can be used only by member functions. The second, public, part constitutes the public interface to objects of the class. A struct is simply a class whose members are public by default; member functions can be defined and used exactly as before. Nonmember functions are barred from using private members.

There are two reasons for controlling access to members. The first is to keep the client programmer's hands off tools they shouldn't touch, tools that are necessary for the internal machinations of the data type, but not part of the interface the client programmer needs to solve their particular problems. This is actually a service to client programmers because they can easily see what's important to them and what they can ignore.

The second reason for access control is to allow the library designer to change the internal workings of the structure without worrying about how it will affect the client programmer.

The public keyword means all member declarations that follow are available to everyone.

The private keyword, on the other hand, means that no one can access that member except you, the creator of the type, inside function members of that type. private is a brick wall between you and the client programmer; if someone tries to access a private member, they'll get a compile-time error.

The last access specifier is protected. protected acts just like private, with one exception that we can't really talk about right now: inherited structures (which cannot access private members) are granted access to protected members.

Constructors

...

Self-reference

...

Copy-constructor

...


Guaranteed initialization with the constructor

In C++, initialization is too important to leave to the client programmer. The class designer can guarantee initialization of every object by providing a special function called the constructor. If a class has a constructor, the compiler automatically calls that constructor at the point an object is created, before client programmers can get their hands on the object. The constructor call isn't even an option for the client programmer; it is performed by the compiler at the point the object is defined.

The name of the constructor is the same as the name of the class. It makes sense that such a function will be called automatically on initialization.

class X { int x; public: X () { x = 0; } // the default constructor X (int x) { this->x = x; } // the constructor };
The default constructor

A default constructor is a constructor that can be called without supplying an argument.

class X { int x; public: X (int x=0) { this->x = x; } // the default constructor };
The default constructor is so important that the compiler will try to generate one if needed and if the user hasn't declared other constructors. A compiler-generated default constructor implicitly calls the default constructors for all data members (which are classes) in the class.

If any constructors are defined, however, and there's no default constructor. Because constants and references must be initialized, a class containing constant or reference members cannot be default-constructed unless the programmer explicitly supplies a constructor.

class X { const int a; const int &r; // no default constructor for X };
The constructor initializer list

The special initialization point is called the constructor initializer list. The constructor initializer list - which, as the name implies, occurs only in the definition of the constructor - is a list of constructor calls that occur after the function argument list and a colon (but before the opening brace of the constructor body). This is to remind you that the initialization in the list occurs before any of the main constructor code is executed. This is the place to put all constants and references initializations.

class X { const int i; const int &r; public: X (int ii, int &rr) : i(ii), r(rr) {} };
Guaranteed cleanup with the destructor

A constructor initializes an object. In other words, it creates the environment in which the member functions operate. Sometimes, creating that environment involves acquiring a resource - such as a file, a lock, or some memory - that must be released after use. Thus, some classes need a function that is guaranteed to be invoked when an object is destroyed in a manner similar to the way a constructor is guaranteed to be invoked when an object is created. Inevitably, such functions are called destructors. They typically clean up and release resources. Destructors are called implicitly when an automatic variable goes out of scope, an object on the free store is deleted, etc.

class X { double array; int size; public: X (int s=1) { array = new double[size=s]; } ~X () { delete []array; } // the destructor };

The destructor never has any arguments because destruction never needs any options.

Constant data members in classes

One of the places you'd like to use a const for constant expressions is inside classes. The use of const inside a class means "this is constant for the lifetime of the object". However, each different object may contain a different value for that constant.

class X { public: const double val; // constant data member public: X (int v) : val(v) {} };

Because a const must be initialized at the point it is created, inside the main body of the constructor the const must already be initialized. The constructor initializer list is the place where you can initialize constant data member.

Constant member functions in classes

The const after the argument list in the member function declarations indicates that these constant functions do not modify the state of an object.

class X { double x; public: X () { x = 0.0; } public: double value () const; // a constant member function double add (int d=1.0); }; double X::value () const { return x; } double X::add (int d) { return x+=d; }

A const member function can be invoked for both const and non-const objects, whereas a non-const member function can be invoked only for non-const objects.

X a; const X c; cout << a.value() << endl; cout << c.value() << endl; a.add(0.001); c.add(-0.001); // error: cannot change value of const c
In-class function definitions

A member function defined within the class definition - rather than simply declared there - is taken to be an inline member function.

class X { double x; public: X () { x = 1.0; } public: double value () const { return x; } // an inline member function };

You can define the inline member functions after the class itself.

class X { double x; public: X () { x = 1.0; } public: inline double value () const; // an inline member function }; inline double X::value () const { return x; }

References
  1. B.Stroustrup: The C++ programming language. Third edition.
    Section 10: classes; pp. 223-258.
  2. Bruce Eckel: Thinking in C++. Second edition.
    Section 4: data abstarction; pp. 217-254.
    Section 5: hiding the implementation; pp. 259-280.
    Section 6: initialization and cleanup; pp. 283-306.
    Section 8: constants; pp. 352-367.
    Section 11: references and copy-constructor; pp. 455-479.