about
10/20/2022
C++ Story Classes
Chapter #5 - C++ Classes
classes and value types
5.0 Prologue
Quick Starter Example - Simple Logger
- Use of the "= delete" suffix, discussed later in this chapter.
- Use of std::ostream pointer data member, but casting that to a derived std::fstream pointer with dynamic_cast in the open method and destructor. Doing this allows us to log to std::cout, a std::ostream instance, but also log to derived std::fstreams. We discuss this in chapter 5, and will refer back to this example at that time.
5.1 Basic Classes
5.1.1 Class Methods
Table 1. - Special Class Methods for Class X
Method Syntax | Description |
---|---|
|
Default constructor builds instance of X with default state values. |
|
Copy constructor builds instance of X with copy of the state of an existing instance. |
|
Move constructor builds an instance and transfers ownership of a source object's state. |
|
Copy assignment copies the state of a source instance to the state of an existing destination instance. |
|
Move assignment transfers ownership of a source instance's state to the destination instance. Source object becomes invalid. |
|
Destructor returns class resources when instance goes out of scope. |
5.1.2 Point Class Examples
5.1.3 Point Examples Code
Example1: Point
- Class methods are defined in-line in the body of the class declaration, Java style. That makes reading the interface harder than necessary. It's better C++ style to move method definitions out of the class declaration.
- This is an unnecessarily inflexible design. Defining the class's coordinate buffer as a composed member means that we must define the array size at compile time, because the array will be placed in static memory and the compiler does that.
Example2: Revised Point
- Method definitions are factored out of Example2::Point class declaration which makes the class interface easier to comprehend. We've also provided a prologue comment for each method.
- We've added an index operator for constant points, as without that, using code cannot index a const point.
- Like Example1::Point, this implementation doesn't need a copy constructor, copy assignment operator, or destructor since the compiler generated methods work correctly. The reason for that is that all the data members have correct copy, assignment, and destruction semantics - all of the STL containers, like std::string and std::vector, have those properties.
Example3: Point Revised Again
- Now, instead of composing the data buffer, as we did in Example1::Point, we aggregate it, e.g., we point to a heap-based buffer with the pointer pBuffer_.
- We are now obliged to provide a copy constructior, copy assignment operator, and destructor. The presence of the pointer, pBuffer_, means that all the class data members no longer have correct copy, assignment, and destruction semantics. The class's generated methods would simply copy and assign pBuffer_, not what it points to. That would result in incorrect operation, as you can verify by commenting out those operations and running code, in the Chapter4-classes folder.
- The example shows you how to implement copy, assignment, and destruction operations for this common scenario.
- The Point design is now more complex that either of the first two, since we now must implement copies, assignment, and destruction. We would probably do some performance testing to be sure the improvement over Example2::Point was significant enough to warrant this choice.
5.2 Value Types
- Providing compiler generated copy constructors, copy assignment operators, and destructors, for cases where all the class's base classes and data members have correct copy, assignment, and destruction semantics. For those cases, e.g., data members are fundamental types and/or STL containers, you should not implement those methods - the compiler will implement them correctly.
- When data members do not have correct copy, assignment, and destruction behaviours, the language supports definition of those operations as class methods, as we have done in Example3::Point. In these cases, e.g., when the class holds a pointer member to data in the native heap, you either implement those methods or disallow them, preventing the compiler from generating them.
-
The compiler will not generate constructors or assignment operators if you
declare them and use a
= delete suffix.
5.3 Scope-based Resource Allocation
5.4 Class Anatomy Example
X x2 = x1;
X x2{ x1 };
Class Anatomy Code
X temp;
// configure temp
return temp;
} X x = makeAnX();
x = makeAnX();
-
lvalue and rvalue references were part of the C language lore. An lvalue is a
named instance that would be defined on the left side of a declaration, e.g.:
int i{5}; i = 7; These classifications become slightly more complicated for C++. See Lvalues and Rvalues
5.5 Compiler-Generated Methods
- copy constructor and copy assignment operator to support value behavior
- move constructor and move assignment operator to improve return value performance
- destructor to support release of resources
- default constructor to support basic creation
Table 2. - Compiler generated methods
Syntax | Conditions |
---|---|
|
Default Constructor: Compiler will generate if, and only if, no constructors are declared. |
|
Copy Constructor: Compiler will always generate as needed if not declared by class. |
|
Move Constructor: Compiler will generate if, and only if, no copy and no move constructors and assignment operators are declared. |
|
Copy Assignment: Compiler will always generate as needed if not declared by class. |
|
Move Assignment: Compiler will generate if, and only if, no copy and no move constructors and assignment operators are declared. |
|
Destructor: Compiler will always generate if not declared. |
Table 3. - When Value Methods should (not) be implemented
Define Value Methods | When | Examples |
---|---|---|
Allow compiler to generate methods | Bases and member data have correct copy, assignment, and destruction semantics. | All data members in class and its base classes are fundamental types, arrays of fundamental types, or any of the STL container classes. |
Designer provides methods | At least one base class is not a value type or at least one data member does not have correct copy, assignment, and destruction semantics. | Class contains pointer to resources stored on heap. |
Designer disables value methods: |
A data member is not copyable or assignable. |
Data member is |
- Let the compiler generate methods - true for a large fraction of the classes we design.
- Provide the methods, as we did in Example3::Point.
- Disable copy and assignment with the
= delete suffix.
5.6 Class Examples from the Repositories
Table 4. - Examples from C++ Repositories
Code Source | Description |
---|---|
CppStoryRepo.html | Contains all code used for illustration in the chapter. |
STRCode.html | Shows how to implement all of the methods needed to make a value type. The documentation page provides a lot of that information. |
CppBasicDemos.html | Demonstrates: pointers and references, lambdas, callable objects, storage sizes, modern casts, alternate constructor syntax (Equiv), and class layout. |
FileManager.html | Contains five projects that illustrate alternate ways of building a directory explorer. Much like the Point projects in Section 4.1, they explore ways of making code flexible and effective. |
CppGraph.html | Builds a directed graph and provides methods for processing data in each vertex and edge. |
ObjectFactories.html | Demonstrates code for Dependency Inversion: Interfaces and Object Factories. |
STL-Containers.html | Simple demonstrations of use for each of the STL containers and adapters. |
CppConcurrentFileAccess.html | This component attempts to open a file for either reading or writing. If open fails, the component sleeps for a while, then attempts to open again, trying a finite number of times. |
5.7 Epilogue
5.8 Programming Exercises
-
Write a class that has a single implemented method,
void title(const std::string& msg) . The method should format the title, as in Exercises-1:7.
How many methods does this class have? Remember that the compiler will generate certain constructors, a copy assignment operator, and a destructor. The answer depends on whether you stored the msg string as a member of the class - no need to do that for a title, but you might want to add other functionality to the class later. Should you implement the compiler generated methods? -
For the class you created in the first exercise, add a method called
add(const std::string& text) that accepts a std::string and appends it to a private std::string member. Also, add a method to return the std::string member by value.
How should you initialize the std::string member? Can you write the "add" method so that it returns a reference an invoking instance of the class? That will allow you to chain calls, e.g.,x.add(": one").add(", two") . -
Build a class with method
bool top(const std::string& fileSpec, size_t n) . Attempt to open the file, and if that succeeds, display the first N lines.
Please handle file opening errors in some meaningful way. -
Using code from the FileDates repository,
build a class that finds all the files within a specified range of dates.
What you are asked to do is fairly easy to implement, so take the time to look carefully at the design of the FileDates compound object.
5.9 References
Posts on Fluent C++
C++ Idioms
C++ weekly videos