|
BDE 4.14.0 Production release
|
Provide a type that tracks if it's been copied or moved
This component provides a class, bsltf::CopyMoveTracker, that keeps track of whether it has been copied to, moved to, or moved from. It is useful for test drivers to ensure that copy and move operations are invoked when expected and not invoked when not expected.
Each object of type CopyMoveTracker contains a bit mask of type bsltf::CopyMoveState::Enum. The constructors and manipulators clear all of the bits, then set them as follows:
In addition, the move constructor and move assignment operator modify the moved-from object by setting the e_MOVED_FROM bit in addition to any other bits that might already be set. Thus, if the e_MOVED_FROM bit is set, then the most recent change was caused by a move-from operation. Conversely, if a moved-from object is the target of an (copy or move) assignment, the e_MOVED_FROM bit is cleared.
The CopyMoveTracker class is an in-core value-semantic type having no salient attributes, and therefore only one value. The state of a CopyMoveTracker object is irrelevant to its value, so all CopyMoveTracker objects compare equal. A CopyMoveTracker subobject of a larger client class does not contribute to that class's value (see Usage Example 1, below), but the subobject provides all of the necessary value-semantic operations, allowing the client class to default the copy and move constructors, the copy and move assignment operators, and/or (in C++20) the equality comparison operators.
When building a new type that tracks copy and move operations, it is tempting to include CopyMoveTracker via inheritance. Indeed, this class provides features to make inheritance convenient, including providing numerous fine-grained accessors named so as to avoid conflicts with derived-class members. By inheriting from CopyMoveTracker, these accessors become available in the derived class without manually defining forwarding functions.
The convenience of using inheritance should be balanced against contravening factors. The simultaneous use of interface and implementation inheritance is rarely a good design decision and is generally discouraged in our production code base. Inheriting from this class subjects the user to the potential dangers of slicing, e.g., assigning to, or moving from, a derived-class object through a base-class reference. Such issues can arguably be avoided fairly easily in relatively small, single-translation-unit programs such as test drivers, but users should be aware of the peril. Inheriting from CopyMoveTracker should be avoided in public header files.
To simplify the use of composition, which is preferred over inheritence, the CopyMoveState component on which this one is built provides a set of psuedo accessors that mirror the ones provided by CopyMoveTracker. A client class MyType enables these psuedo accessors by defining an ADL customization point named copyMoveState(const MyType&) in the namespace in which MyType is defined. With this customization point in place, a client program can call bsltf::CopyMoveState::isMovedInto(obj) instead of obj.isMovedInto(). Using a short alias for bsltf::CopyMoveState, a call to a psuedo accessor for an object of MyType is scarcely more verbose than a call to a real accessor within MyType, without using inheritance and without writing a large number of forwarding functions within MyType. The usage examples below show the use of composition applying this technique.
This section illustrates intended use of this component.
In this example, we create a class that holds an integer value and tracks moves and copies.
First, we define the class and it's data members, including a CopyMoveTracker to keep track of the moves and copies:
Next, we define the constructors for TrackedValue such that they forward appropriately the CopyMoveTracker member appropriately:
For this example, we don't need to define the assignment operators, but their implemenation would be similar to the corresponding constructors.
Next, we define an accessor to return the value of our tracked value.
Then, we define a hidden friend function, copyMoveState, that returns the copy/move state. This friend function is an ADL customization point that allows CopyMoveState::get(obj) to return the copy/move state when obj is a tracked value and allows boolean psuedo-accessors such as CopyMoveState::isMovedFrom(obj) to query the copy/move state:
Next, we define equality-comparison operators for TrackedValue. Note that only the value attribute is compared; the copy/move state is not a salient attribute of the class and is thus not part of its value:
Now we use TrackedValue in a program, beginning by constructing a variable. The variable is in the not-copied-or-moved state:
Finally, we make a copy of our TrackedValue variable. The copy is in a copied-into state, but it still has the same value as tv1:
In this example, we test a simple wrapper template, CountedWrapper<T>, that holds an object of type T and counts the number of such wrapper object currently live in the program. We begin by sketching the wrapper template being tested (with unnecessary details left out):
Next, we instantiat our wrapper with the TrackedValue class from Example 1 so that we can track whether the argument passed to the wrapper constructor is correctly passed to the wrapped object, including preserving its value category:
Next, we check that a value-constructed wrapper results in a tracked value that has not be copied or moved; i.e., no temporaries were created and then copied. Checking the copy/move state requires calling static methods of bsltf::CopyMoveState; we make such calls terser by defining Cms as an abbreviation for bsltf::CopyMoveState:
Next, we check that a wrapper constructed from a TrackedValue variable forwards the variable as an lvalue, resulting in a call to the copy constructor. We also check that, in C++11, the lvalue is perfectly forwarded as a non-const lvalue:
Finally, we check that a wrapper constructed from a moved TrackedValue forwards the variable as an rvalue, resulting in a call to the move constructor. Note that original variable is also modified by this operation: