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: