Chapter 7. Classes
7.1 Defining Abstract Data Types
Data abstraction is a programming technique that relies on the separation of interface and implementation. The interface of a class consists of the operations that users of the class can execute. The implementation includes the class's data members, the bodies of the functions that constitute the interface, and any functions needed to define the class that are not intended for general use.
Encapsulation enforces the separation of a class's interface and implementation. A class that is encapsulated hides its implementation, users of the class can use the interface but have no access to the implementation.
7.1.1 Designing the Class
C++ programmers tend to speak of users interchangeably as users of the application or users of a class.
7.1.2 Defining the Revised Class
Functions defined in the class are implicitly inline.
The member function's body can be defined inside or outside of the class body. We can use the dot operator to fetch the member of the object.
Member functions access the object on which they were called through an extra, implicit parameter named this. When we call a member function, this is initialised with the address of the object on which the function was invoked.
this
a a const pointer, we cannot change the address that this holds. this
is implicit and does not appear in the parameter list. There is no place to indicate that this
should be a pointer to const. The language resolves this problem by letting us put const after the parameter list of a member function. Member functions that use const in this way are const member functions:
The definitions of the member functions of a class are nested inside the scope of the class itself.
The member's definition must match its declaration when we define a member function outside the class body.
7.1.3 Defining Nonmember Class-Related Functions
Some functions define operations that are conceptually part of the interface of the class, but they are not part of the class itself.
7.1.4 Constructors
Classes control object initialisation by defining one or more special member function known as constructor.
Constructors have the same name as the class. Unlike other functions, constructor have no return type. Like other functions, constructor have a parameter list and a function body. A class can have multiple constructors. Constructors may not be declared as const.
Classes control default initialisation by defining a special constructor, known as the default constructor. The default constructor is one that takes no arguments. If our class does not explicitly define any constructors, the compiler will implicitly define the default constructor for us. The compiler-generated constructor is known as the synthesised default constructor.
The compiler generates a default constructor automatically only if a class declares no constructors.
The second reason to define the default constructor is that for some classes, the synthesised default constructor does the wrong thing.
A third reason that some classes must define their own default constructor is that sometimes the compiler is unable to synthesise one.
Constructors have no return type, so this definition starts with the name of the function we are defining. Members that do not appear in the constructor initialiser list are initialised by the corresponding in-class initialiser or are default initialised.
7.1.5 Copy, Assignment, and Destruction
In addition to defining how objects of the class type are initialised, classes also control what happens when we copy, assign, or destroy objects of the class type. If we do not define these operations, the compiler will synthesise them for us. The synthesised versions are unlikely to work correctly for classes that allocate resources that reside outside the class objects themselves.
The synthesised version for copy, assignment, and destruction work correctly for classes that have vector or string members.
7.2 Access Control and Encapsulation
C++ uses access specifiers to enforce encapsulation:
Members defined after a public specifier are accessible to all parts of the program. The public members define the interface to the class.
Members defined after a private specifier are accessible to the member functions of the class but are not accessible to code that uses the class. The private sections encapsulate the implementation.
The only difference between struct and class is the default access level. If we use the struct keyword, the members defined before the first access specifier are public; if we use class, then the members are private.
7.2.1 Friends
A class can allow another class or function to access its nonpublic members by making that class or function a friend. A class makes a function its friend by including a declaration for that function preceded by the keyword friend:
Friend declarations may appear only inside a class definition; they may appear anywhere in the class. Friends are not members of the class and are not affected by the access control of the section in which they are declared.
Encapsulation provides two important advantages:
User code cannot inadvertently corrupt the state of an encapsulated object.
The implementation of an encapsulated class can change over time without requiring changes in user-level code.
To make a friend visible to users of the class, we usually declare each friend in the same header as the class itself.
7.3 Additional Class Feature
7.3.1 Class Members Revisited
Defining a Type Member:
Member Functions of Class
It can ask the compiler to synthesise the default constructor's definition by use = default
.
Making Members inline
Classes often have small functions that can benefit from being inlined. Member functions defined inside the class are automatically inline.
mutable Data Members
A mutable data member is never const, even when it is a member of a const object.
Initialiser for Data Members of Class Type
Under the new standard the best way to specify this default value is as an in-class initialiser:
7.3.2 Function That Return *this
Functions that return a reference are lvalues, which means that they return the object itself, not a copy of the object.
A const member function that returns *this as a reference should have a return type that is reference to const.
Overloading Based on const
We can overload a member function based on whether it is const for the same reasons that we can overload a function based on whether a pointer parameter points to const.
7.3.3 Class Types
The forward declaration introduces the class name Screen
into the program and indicated that Screen
refers to a class type. After a declaration and before a definition is seen, the type Screen
is an incomplete type, which is known that Screen
is a class type but not known that members that type contains.
The incomplete type is limited: we can define pointer or references to such type, and we can declare functions that use an incomplete type as a parameter or return type.
7.3.4 Friendship Revisited
A friend function can be defined inside the class body.
Even if we define the function inside the class, we must still provide a declaration outside of the class itself to make that function visible.
7.4 Class Scope
The name lookup has been relatively straightforward:
First, look for a declaration of the name in the block in which the name was used. Only names declared before the use are considered.
If the name isn't found, look in the enclosing scope.
If no declaration is found, then the program is in error.
Member function definitions are processed after the compiler processes all of the declarations in the class.
Type Name Are Special
In a class, if a member uses a name from an outer scope and that name is a type, then the class may not subsequently redefine that name:
Although it is an error to redefine a type name, compilers are not required to diagnose this error.
Normal Block-Scope Name Lookup inside Member Definitions
A name used in the body of a member function is resolved as follows:
First, look for a declaration of the name inside the member function. As usual, only declarations in the function body that precede the use of the name are considered.
If the declaration is not found inside the member function, look for a declaration inside the class. All the members of the class are considered.
If a declaration for the name is not found in the class, look for a declaration that is in scope before the member function definition.
7.5 Constructors Revisited
7.5.1 Constructor Initialiser List
We must use the constructor initialiser list to provide values for members that are const, reference, or of a class type that does not have a default constructor.
By routinely using constructor initialisers, you can avoid being surprised by compile-time errors when you have a class with a member that requires a constructor initialiser.
Members are initialised in the order in which they appear in the class definition. The order of initialisation often doesn't matter. However, if one member is initialised in terms of another, then the order in which members are initialised is crucially important.
It is a good idea to write constructor initialiser in the same order as the members are declared. Moreover, when possible, avoid using members to initialise other members.
A constructor that supplies default arguments for all its parameters also defines the default constructor.
7.5.2 Delegating Constructors
A delegating constructor uses another constructor from its own class to perform its initialisation.
7.5.3 The Role of the Default Constructor
Default initialisation happens:
When we define non-static variable or arrays at block scope without initialisers.
When a class that itself has members of class type uses the synthesised default constructor.
When members of class type are not explicitly initialised in a constructor initialiser list.
Value initialisation happens:
During array initialisation when we provide fewer initialiser than the size of the array.
When we define a local static object without an initialiser.
When we explicitly request value initialisation by writing an expressions of the form T() where T is the name of a type.
7.5.4 Implicit Class-Type Conversions
The compiler will automatically apply only one class-type conversion.
We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor as explicit.
One context in which implicit conversions happen is when we use the copy form of initialisation. We cannot use nan explicit constructor with this form of initialisation; we must use direct initialisation:
Some of the library classes that we've used have single-parameter constructors:
The string constructor that takes a single parameter of type const char* is not explicit.
The vector constructor that takes a size is explicit.
7.5.5 Aggregate Classes
An aggregate class gives users direct access to its members and has special initialisation syntax. A class is an aggregate if:
All of its data members are public.
It does not define any constructors.
It has no in-class initialisers.
It has no base classer or virtual functions.
We can initialise the data members of an aggregate class by providing a braced list of member initialiser, the initialisers must appear in declaration order of the data members.
It is worth noting that there are three significant drawbacks to explicitly initialising the members of an object of class type:
It requires that all the data members of the class be public.
It puts the burden on the user of the class to correctly initialise every member of every object.
If a member is added or removed, all initialisations have to be updated.
7.5.6 Literal Classes
Although constructors can't be const, constructors in a literal class can be constexpr function. Indeed, a literal class must provide at least one constexpr constructor.
A constexpr constructor can be declared as = default
.
We define a constexpr constructor by preceding its declaration with the keyword constexpr:
A constexpr constructor must initialise every data member. The initialisers must either use a constexpr constructor or be a constant expression.
A constexpr constructor is used to generate objects that are constexpr and for parameters or return types in constexpr functions:
7.6 static Class Members
The static member can be public or private. The type of a static data member can be const, reference, array, class type, and so forth.
The static members of a class exist outside any object. Objects do not contain data associated with static data members. Similarly, static member functions are not bound to any objects; they do not have a this pointer.
Member function can use static members directly, without the scope operator.
The static member function can be defined inside or outside of the class body. The keyword appears only with the declaration inside the class body.
The static data member must be defined initialised outside the class body.
The in-class initialisers for static members that have const integral type and must do so for static members that are constexpr of literal type. The initialisers must be constant expressions.
If the member is used only in contexts where the compiler can substitute the member's value, then an initialised const or constexpr static need not be separately defined.
If an initialiser is provided inside the class, the member's definition must not specify an initial value:
A static data member can have incomplete type, it can have the same type as the class type of which it is a member. A non-static data member is restricted to being declared as a pointer or a reference to an object of its class.
Last updated
Was this helpful?