BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bsltf_copymovetracker

Detailed Description

Outline

Purpose

Provide a type that tracks if it's been copied or moved

Classes

See also
bsltf_copymovestate, bsltf_argumenttype

Description

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:

e_MOVED_FROM ----------------------.
e_MOVED_INTO ------------------. |
Bits set: e_COPIED_NONCONST_INTO ----. | |
e_COPIED_CONST_INTO ---. | | |
e_COPIED_INTO -----. | | | |
Operation: V V V V V
+------------------------------------------+---+---+---+---+---+
| CopyMoveState() | | | | | |
| resetCopyMoveState() | | | | | |
+------------------------------------------+---+---+---+---+---+
| CopyMoveState(const CopyMoveState&) | X | X | | | |
| operator=(const CopyMoveState&) | X | X | | | |
+------------------------------------------+---+---+---+---+---+
| CopyMoveState(CopyMoveState&) | X | | X | | |
| operator=(CopyMoveState&) | X | | X | | |
+------------------------------------------+---+---+---+---+---+
| CopyMoveState(MovableRef<CopyMoveState>) | | | | X | |
| operator=(MovableRef<CopyMoveState>) | | | | X | |
+------------------------------------------+---+---+---+---+---+

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.

Use via composition vs. inheritance

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.

Usage

This section illustrates intended use of this component.

Example 1: A tracked value class

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:

class TrackedValue {
int d_value;
Definition bsltf_copymovetracker.h:353

Next, we define the constructors for TrackedValue such that they forward appropriately the CopyMoveTracker member appropriately:

public:
explicit TrackedValue(int v = 0) : d_tracker(), d_value(v) { }
TrackedValue(const TrackedValue& original)
: d_tracker(original.d_tracker), d_value(original.d_value) { }
TrackedValue(TrackedValue& original)
: d_tracker(original.d_tracker), d_value(original.d_value) { }
TrackedValue(bslmf::MovableRef<TrackedValue> original)
: d_tracker(bslmf::MovableRefUtil::move(
bslmf::MovableRefUtil::access(original).d_tracker))
{
TrackedValue& originalLvalue = original;
d_value = originalLvalue.d_value;
originalLvalue.d_value = -1;
}
Definition bslmf_movableref.h:751
Definition bdlbb_blob.h:576

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.

int value() const { return d_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:

friend bsltf::CopyMoveState::Enum copyMoveState(const TrackedValue& v)
{ return v.d_tracker.copyMoveState(); }
};
Enum
Definition bsltf_copymovestate.h:222

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:

bool operator==(const TrackedValue &a, const TrackedValue &b)
{
return a.value() == b.value();
}
bool operator!=(const TrackedValue &a, const TrackedValue &b)
{
return a.value() != b.value();
}
#define BSLS_ANNOTATION_UNUSED
Definition bsls_annotation.h:287

Now we use TrackedValue in a program, beginning by constructing a variable. The variable is in the not-copied-or-moved state:

int main()
{
TrackedValue tv1(99);
assert(99 == tv1.value());
static bool isOriginal(const TYPE &v)
Definition bsltf_copymovestate.h:401

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:

TrackedValue tv2(tv1);
assert(99 == tv2.value());
assert(tv2 == tv1);
}
static bool isCopiedInto(const TYPE &v)
Definition bsltf_copymovestate.h:376
static bool isCopiedNonconstInto(const TYPE &v)
Definition bsltf_copymovestate.h:382

Example 2: Testing a wrapper template

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):

#include <bslmf_util.h>
template <class TYPE>
class CountedWrapper {
// Hold an object of 'TYPE' and count the number of objects.
// CLASS DATA
static int s_count;
// DATA
TYPE d_object;
public:
// CLASS METHODS
static int count() { return s_count; }
// CREATORS
CountedWrapper() { ++s_count; }
template <class ARG>
explicit
CountedWrapper(BSLS_COMPILERFEATURES_FORWARD_REF(ARG) ctorArg)
// Construct the wrapped object by forwarding the specified
// 'ctorArg' to the constructor for 'TYPE'.
: d_object(BSLS_COMPILERFEATURES_FORWARD(ARG, ctorArg))
{ ++s_count; }
~CountedWrapper() { --s_count; }
// ...
// ACCESSORS
const TYPE& object() const { return d_object; }
};
template <class TYPE>
int CountedWrapper<TYPE>::s_count = 0;
#define BSLS_COMPILERFEATURES_FORWARD_REF(T)
Definition bsls_compilerfeatures.h:2012
#define BSLS_COMPILERFEATURES_FORWARD(T, V)
Definition bsls_compilerfeatures.h:2018

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:

typedef CountedWrapper<TrackedValue> WrappedValue;

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:

int main()
{
typedef bsltf::CopyMoveState Cms;
WrappedValue wv1(99); // Default constructor
assert(1 == WrappedValue::count());
assert(99 == wv1.object().value());
assert(Cms::isOriginal(wv1.object()));
Definition bsltf_copymovestate.h:219

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:

TrackedValue t2(44);
assert(Cms::isOriginal(t2));
WrappedValue wv2(t2);
assert(44 == t2.value()); // Unchanged
assert(Cms::isOriginal(t2)); // Unchanged
assert(2 == WrappedValue::count());
assert(44 == wv2.object().value());
assert(Cms::isCopiedInto(wv2.object())); // Copy constructed
#ifdef BSLS_COMPILERFEATURES_SUPPORT_RVALUE_REFERENCES
// Copy constructed from non-const original
assert(Cms::isCopiedNonconstInto(wv2.object()));
#endif

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:

TrackedValue t3(t2);
assert(44 == t3.value());
assert(Cms::isCopiedNonconstInto(t3));
WrappedValue wv3(bslmf::MovableRefUtil::move(t3));
assert(-1 == t3.value());
assert(Cms::isCopiedNonconstInto(t3));
assert(Cms::isMovedFrom(t3));
assert(3 == WrappedValue::count());
assert(44 == wv3.object().value());
assert(Cms::isMovedInto(wv3.object()));
}
static MovableRef< t_TYPE > move(t_TYPE &reference) BSLS_KEYWORD_NOEXCEPT
Definition bslmf_movableref.h:1060