Abstraction and Resource Management in C++
A resource handle is an object that holds a resource on behalf of the code using it.
Types for the Ideas in a Program
C++ programs work with types. Some types, such as built-in number types, come with the language. C++ also lets programmers define named types of their own, called user-defined types. These let a program represent an idea from its problem domain, such as a bank account, a shape, or a sensor reading, as a type.
| Type family | Where it comes from | What it lets a program represent |
|---|---|---|
| Built-in type | Provided by C++ | A built-in value such as a number |
| User-defined type | Defined by the programmer | A named idea from the program's problem domain |
The distinction is about where the type comes from: C++ provides built-in types, while the programmer defines user-defined types.
Concrete classes, abstract classes, and class hierarchies are three useful ways to think about user-defined types. The first question to ask about a class is whether you can create an object from it. That question separates concrete classes from abstract classes; inheritance explains how related classes can share a structure.
Whether a Class Can Become an Object
A concrete class is not abstract: you can create an object from it. The object is an instance of that type and can hold data and respond to operations defined by the class. An abstract class cannot be instantiated on its own. Its purpose is to provide a common base for classes that derive from it; objects are created from those derived classes instead.
Classifying a shape hierarchy
A program has a general Shape class and more specific Circle, Square, and Triangle classes that inherit from it. What can you conclude about their relationships, and what test would decide whether each class is concrete or abstract?
- 1Read the inheritance
Shape is the general type, and Circle, Square, and Triangle are specific types that inherit from it.
- 2Apply the instantiation test
For each class, ask whether an object can be created from that class itself. If yes, it is concrete; if no, it is abstract.
The inheritance relationships identify a hierarchy, but the names alone do not establish which classes are abstract or concrete. Use the object-creation test for each class.
Do not classify a class just from its name or its place in a hierarchy. The decisive test is whether an object can be created from it.
The Constructor-to-Destructor Responsibility
A class may need a limited resource, such as memory, a file, or a lock, to do its job. If its constructor acquires that resource, the class has taken on a responsibility: the resource must later be released. The destructor is the natural place for that release, because it marks the end of the object's lifetime.
The sequence ties the resource's start and end to the object's start and end. Acquisition happens during construction; release belongs in the destructor. With that responsibility attached to the object's lifetime, the program does not depend on a separate hand-written release step being reached. If the destructor does not release a resource acquired by the constructor, the resource can remain held after the object that needed it is gone.
Checking a resource-owning class
Suppose a class acquires an array on the heap in its constructor. What responsibility follows from that acquisition?
- 1Find the acquisition
The constructor takes control of the array resource.
- 2Find the matching release
The class needs a destructor that releases the resource when the object is destroyed.
The class should pair its constructor's acquisition with release in its destructor.
RAII and Resource Handles
RAII stands for Resource Acquisition Is Initialization. It is the pattern in which a resource is acquired during an object's initialization, so the object's lifetime controls the resource's lifetime.
A resource handle is an object that holds a resource on behalf of the code using it. The handle acquires the resource when it is created and releases it when it is destroyed. Calling code works through the handle rather than taking on the release bookkeeping itself. Requests and values pass from the caller through the handle to the resource; results travel back through the handle to the caller.
The key difference is who carries the release responsibility. With naked operations, the programmer must arrange the matching release. With a resource handle, the handle's destruction releases the resource, and the caller uses the resource through the handle. This describes the management pattern without requiring a particular handle type.
Creating a Container with Initial Values
A container class holds a collection of elements. An initializer-list constructor accepts values written in braces, allowing a container to be created already filled with those values. The elements come from the braced list, so the object is populated at creation time. This makes small collections easier to create and read.
Reasoning about a container initializer
A container class is given an initializer-list constructor, and a caller supplies values in braces when creating the container. What should you expect about the new object?
- 1Identify the constructor input
The braced list supplies values to the initializer-list constructor.
- 2Follow the values into the object
The container's elements come from that list, so it is populated as it is created.
The container begins with the supplied values as its elements.
Mistakes in Classification and Resource Management
Common mistake Calling a class concrete because it is a specific-sounding type.
What it looks likeAssuming that Circle must be concrete just because it names a specific kind of shape.
Why it failsA class's name does not establish whether objects can be created from it.
Do this insteadCheck whether an object can be created from the class itself. If yes, it is concrete; if no, it is abstract.
Common mistake Treating an abstract class as though it were an object type that can be instantiated directly.
Why it failsAn abstract class cannot be instantiated on its own; it serves as a base for derived classes.
Do this insteadCreate objects from concrete classes that derive from the abstract class.
Common mistake Acquiring a resource in a constructor without providing for its release.
Why it failsThe resource can remain held after the object that needed it is gone.
Do this insteadGive the class a destructor that releases the resource acquired by the constructor.
Common mistake Writing naked new and assuming a matching delete will take care of itself.
Why it failsThe programmer must ensure that every required matching delete happens on every path.
Do this insteadPrefer a resource handle using RAII so release is tied to the handle's destruction.
Common mistake Making a container that accepts only later additions when it should also be convenient to create with initial values.
Why it failsWithout an initializer-list constructor, the container cannot use the described braced-list creation interface.
Do this insteadProvide an initializer-list constructor so the container can start with the supplied elements.
Check Your Understanding
A class named Device acquires a resource in its constructor. The class is intended to be a complete type from which the program creates objects. Explain how you would classify it, what its destructor must do, and how a resource handle using RAII could change who is responsible for release. Then describe what an initializer-list constructor would let a container user provide at creation time.
Hints
- Use the object-creation test to distinguish concrete from abstract.
- Connect constructor acquisition to destructor release.
- A resource handle acquires and releases on behalf of calling code.
- An initializer-list constructor accepts values written in braces.
Key Takeaways
- Built-in types come with C++; user-defined types let programmers represent named ideas from a problem domain.
- A concrete class can be instantiated. An abstract class cannot be instantiated on its own and is intended to serve as a base for derived classes.
- A class hierarchy organizes related types through inheritance, from general classes toward more specific ones.
- When a constructor acquires a resource, the class needs a destructor to release it. RAII and resource handles tie that release to object lifetime.
- Naked new and delete leave matching-release work with the programmer; an initializer-list constructor lets a container start with supplied braced values.