Provide a primitive type trait for bitwise moveable classes.
More...
Namespaces |
namespace | bslmf |
Detailed Description
- Outline
-
-
- Purpose:
- Provide a primitive type trait for bitwise moveable classes.
-
- Classes:
-
- See also:
-
- Description:
- This component provides a single trait metafunction,
bslmf::IsBitwiseMoveable
, which allows generic code to determine whether t_TYPE
can be destructively moved using memcpy
. Given a pointer, p1
, to an object of t_TYPE
, and a pointer p2
of the same type pointing to allocated but uninitialized storage, a destructive move from p1
to p2
comprises the following pair of operations: new ((void*) p2) t_TYPE(*p1);
p1->~t_TYPE();
An object of a t_TYPE
is bitwise moveable, if the above operation can be replaced by the following operation without affecting correctness: std::memcpy(p2, p1, sizeof(t_TYPE));
If IsBitwiseMoveable<t_TYPE>value
inherits from true_type
for a given t_TYPE
, then a generic algorithm can infer that t_TYPE
is bitwise moveable.
- This trait is used by various components for providing optimizations for types that can be bitwise moved. The major benefit of this trait is not for a single object but for an array of such types, as a loop of copy/destroy operations can be replaced by a single call to
memcpy
. This replacement is not only faster, but is guaranteed not to throw an exception.
IsBitwiseMoveable<t_TYPE>
will inherit from true_type
if t_TYPE
is a fundamental object type, enumeration type, or pointer type. Most user-defined classes are bitwise moveable, but generic code must assume that an arbitrary t_TYPE
is not bitwise-moveable, as bitwise moving a type that is not bitwise moveable is likely to result in a dangling pointer. Thus, it is necessary to explicitly associate the bitwise moveable trait with a class (via template specialization or by use of the BSLMF_DECLARE_NESTED_TRAIT
macro) in order for generic algorithms to recognize that class as bitwise moveable. As a special case, one-byte objects are deduced as bitwise moveable unless explicitly annotated otherwise (see-below).
-
- What Classes are Not Bitwise Moveable?:
- A class that has any of the following attributes is not bitwise moveable:
-
Its address is one of the salient attributes that comprise its value.
-
It contains a pointer that might (directly or indirectly) point to itself or to one of its own members or which stores an encoding of its own address or the address of one of its members. For example, a list implementation that includes an embedded sentinel node such that the last node in the list points back to the sentinel node within the list class object is not bitwise moveable.
-
Its constructor registers a pointer to itself in some static registry.
-
Its constructor or destructor have some side effect that, if omitted during destructive move, would render the program incorrect.
-
It contains a data member or base class that is not bitwise moveable.
- Because of the destructive nature of a bitwise move (the original object must be treated as uninitialized storage after the move), a class can be bitwise moveable but not also bitwise copyable. For example, a class that contains a pointer to heap-allocated storage is generally bitwise moveable. The moved object simply refers to the same storage as the (defunct) original. However a bitwise copy of the same object would incorrectly cause the original and the copy to share the same heap-allocated storage.
-
- One-Byte Objects:
- An object whose size does not exceed one byte are deduced to be bitwise moveable. The validity of this heuristic can be deduced by examining the criteria for non-bitwise moveable classes above:
-
Very few types have their own address as a salient attribute.
-
It is not possible for an object to store a pointer to itself in only one byte. It is difficult to conceive of why an object would store a (compressed) encoding of own address in one byte.
-
Static registries of objects of any size are rare and are almost never a good design.
-
Constructors and destructors with side effects are also rare, and appear almost entirely within test code (i.e., counting constructions and destructions). Even in those cases, it is often acceptable or even preferable to skip the balanced side effects of the constructor and destructor during a destructive move.
-
Any data member or base class of a one-byte class must also be either an empty base class or a one-byte object, so the above rationale applies recursively to them.
- The purpose of this heuristic is to deduce bitwise moveability for an important category of empty classes that are not explicitly annotated as being bitwise moveable: standard predicate classes such as
std::less<T>
. Being able to treat these classes as bitwise moveable means that bsl::set
and bsl::map
objects can be deduced as bitwise moveable and that bsl::function
objects wrapping these classes can use the small-object optimization. It can be argued that any type with size less than the size of a pointer should be deduced as bitwise moveable by the logic above. However, it is primarily the common case of empty classes that we are trying to handle. By limiting ourselves to the smallest-possible type, we reduce the chance of false positives (see next paragraph).
- Note that the word "rare" appears several times in the list above. Rare implies non-zero, so we must provide a way to annotate non-bitwise moveable one-byte classes so that the
IsBitwiseMoveable
trait is not deduced for them. This annotation is accomplished simply by specializing IsBitwiseMoveable
to inherit from false_type
for these rare classes.
- In C++11 and later, it is possible to accurately deduce a class is bitwise moveable without relying on the one-byte heuristic. If the deduction with the one-byte heuristic yields true and the deduction without the one-byte heuristic yields false, then a static assert fires and the program is ill-formed. This error can be corrected by specializing the trait to false for the type in question.
-
- Usage:
- This section illustrates intended use of this component.
-
- Example 1: Using the Trait to Implement destructiveMoveArray:
- Here, we use this trait in a simple algorithm called
destructiveMoveArray
, which moves elements from one array to another. The algorithm is implemented using two implementation functions, one for types that are known to be bit-wise moveable, and one for other types. The first takes an extra function argument of type true_type
, the second takes and extra function argument of type false_type
: namespace BloombergLP {
template <class t_TYPE>
void destructiveMoveArrayImp(t_TYPE *to,
t_TYPE *from,
int size,
bsl::true_type)
{
memcpy(static_cast<void *>(to), from, size * sizeof(t_TYPE));
}
template <class t_TYPE>
void destructiveMoveArrayImp(t_TYPE *to,
t_TYPE *from,
int size,
bsl::false_type)
{
for (int i = 0; i < size; ++i) {
::new(to + i) t_TYPE(from[i]);
from[i].~t_TYPE();
}
}
Now we can dispatch between the two Imp functions, using the IsBitwiseMoveable
trait metafunction to determine at compile time which of the implementations should be used: template <class t_TYPE>
void destructiveMoveArray(t_TYPE *to, t_TYPE *from, int size)
{
destructiveMoveArrayImp(to, from, size,
bslmf::IsBitwiseMoveable<t_TYPE>());
}
Next, to check our work, we create three classes that we will use to instantiate destructiveMoveArray
. All of the classes will log the number of constructor and destructor calls. The first class will not be decorated with the IsBitwiseMoveable
trait: class NonMoveableClass
{
private:
int d_value;
static int d_ctorCount;
static int d_dtorCount;
public:
static int ctorCount() { return d_ctorCount; }
static int dtorCount() { return d_dtorCount; }
NonMoveableClass(int val = 0) : d_value(val) { ++d_ctorCount; }
NonMoveableClass(const NonMoveableClass& other)
: d_value(other.d_value) { ++d_ctorCount; }
~NonMoveableClass() { d_dtorCount++; }
int value() const { return d_value; }
};
int NonMoveableClass::d_ctorCount = 0;
int NonMoveableClass::d_dtorCount = 0;
The second class is similar except that we declare it to be bit-wise moveable by specializing IsBitwiseMoveable
: class MoveableClass1
{
private:
int d_value;
static int d_ctorCount;
static int d_dtorCount;
public:
static int ctorCount() { return d_ctorCount; }
static int dtorCount() { return d_dtorCount; }
MoveableClass1(int val = 0) : d_value(val) { ++d_ctorCount; }
MoveableClass1(const MoveableClass1& other)
: d_value(other.d_value) { ++d_ctorCount; }
~MoveableClass1() { d_dtorCount++; }
int value() const { return d_value; }
};
int MoveableClass1::d_ctorCount = 0;
int MoveableClass1::d_dtorCount = 0;
namespace bslmf {
template <> struct IsBitwiseMoveable<MoveableClass1> : bsl::true_type {
};
}
The third class is also declared to be bitwise moveable, but this time we do it using the BSLMF_NESTED_TRAIT_DECLARATION
macro: class MoveableClass2
{
private:
int d_value;
static int d_ctorCount;
static int d_dtorCount;
public:
BSLMF_NESTED_TRAIT_DECLARATION(MoveableClass2,
bslmf::IsBitwiseMoveable);
static int ctorCount() { return d_ctorCount; }
static int dtorCount() { return d_dtorCount; }
MoveableClass2(int val = 0) : d_value(val) { ++d_ctorCount; }
MoveableClass2(const MoveableClass2& other)
: d_value(other.d_value) { ++d_ctorCount; }
~MoveableClass2() { d_dtorCount++; }
int value() const { return d_value; }
};
int MoveableClass2::d_ctorCount = 0;
int MoveableClass2::d_dtorCount = 0;
Finally, invoke destructiveMoveArray
on arrays of all three classes: enum MoveableEnum { A_VALUE };
int usageExample1()
{
using namespace bslmf;
assert( IsBitwiseMoveable<int>::value);
assert( IsBitwiseMoveable<int*>::value);
assert( IsBitwiseMoveable<const int*>::value);
assert( IsBitwiseMoveable<MoveableEnum>::value);
assert(! IsBitwiseMoveable<int&>::value);
assert(! IsBitwiseMoveable<const int&>::value);
assert( IsBitwiseMoveable<MoveableClass1>::value);
assert( IsBitwiseMoveable<const MoveableClass1>::value);
assert( IsBitwiseMoveable<MoveableClass2>::value);
assert( IsBitwiseMoveable<volatile MoveableClass2>::value);
assert(! IsBitwiseMoveable<NonMoveableClass>::value);
assert(! IsBitwiseMoveable<const NonMoveableClass>::value);
const int nObj = 3;
{
NonMoveableClass *p1 = (NonMoveableClass*)
::operator new(nObj * sizeof(NonMoveableClass));
NonMoveableClass *p2 = (NonMoveableClass*)
::operator new(nObj * sizeof(NonMoveableClass));
for (int i = 0; i < nObj; ++i) {
new(p1 + i) NonMoveableClass(i);
}
assert(nObj == NonMoveableClass::ctorCount());
assert(0 == NonMoveableClass::dtorCount());
assert(! IsBitwiseMoveable<NonMoveableClass>::value);
destructiveMoveArray(p2, p1, nObj);
assert(2 * nObj == NonMoveableClass::ctorCount());
assert(nObj == NonMoveableClass::dtorCount());
for (int i = 0; i < nObj; ++i) {
assert(i == p2[i].value());
}
for (int i = 0; i < nObj; ++i) {
p2[i].~NonMoveableClass();
}
::operator delete(p1);
::operator delete(p2);
}
{
MoveableClass1 *p1 = (MoveableClass1*)
::operator new(nObj * sizeof(MoveableClass1));
MoveableClass1 *p2 = (MoveableClass1*)
::operator new(nObj * sizeof(MoveableClass1));
for (int i = 0; i < nObj; ++i) {
::new(p1 + i) MoveableClass1(i);
}
assert(nObj == MoveableClass1::ctorCount());
assert(0 == MoveableClass1::dtorCount());
assert(IsBitwiseMoveable<MoveableClass1>::value);
destructiveMoveArray(p2, p1, nObj);
assert(nObj == MoveableClass1::ctorCount());
assert(0 == MoveableClass1::dtorCount());
for (int i = 0; i < nObj; ++i) {
assert(i == p2[i].value());
}
for (int i = 0; i < nObj; ++i) {
p2[i].~MoveableClass1();
}
::operator delete(p1);
::operator delete(p2);
}
{
MoveableClass2 *p1 = (MoveableClass2*)
::operator new(nObj * sizeof(MoveableClass2));
MoveableClass2 *p2 = (MoveableClass2*)
::operator new(nObj * sizeof(MoveableClass2));
for (int i = 0; i < nObj; ++i) {
::new(p1 + i) MoveableClass2(i);
}
assert(nObj == MoveableClass2::ctorCount());
assert(0 == MoveableClass2::dtorCount());
assert(IsBitwiseMoveable<MoveableClass2>::value);
destructiveMoveArray(p2, p1, nObj);
assert(nObj == MoveableClass2::ctorCount());
assert(0 == MoveableClass2::dtorCount());
for (int i = 0; i < nObj; ++i) {
assert(i == p2[i].value());
}
for (int i = 0; i < nObj; ++i) {
p2[i].~MoveableClass2();
}
::operator delete(p1);
::operator delete(p2);
}
return 0;
}
}
-
- Example 2: Associating a Trait with a Class Template:
- In this example, we associate a trait not with a class, but with a class template. We create three class templates, each of which uses a different mechanisms for being associated with the
IsBitwiseMoveable
trait, plus a "control" template that is not bit-wise moveable. First, we define the non-bit-wise-moveable template, NonMoveableTemplate
: namespace BloombergLP {
template <class t_TYPE>
struct NonMoveableTemplate
{
t_TYPE d_p;
};
Second, we define a MoveableTemplate1
, which uses partial template specialization to associate the IsBitwiseMoveable
trait with each instantiation: template <class t_TYPE>
struct MoveableTemplate1
{
t_TYPE *d_p;
};
namespace bslmf {
template <class t_TYPE>
struct IsBitwiseMoveable<MoveableTemplate1<t_TYPE> > : bsl::true_type {
};
}
Third, we define MoveableTemplate2
, which uses the BSLMF_NESTED_TRAIT_DECLARATION
macro to associate the IsBitwiseMoveable
trait with each instantiation: Fourth, we define MoveableTemplate3
, which is bit-wise moveable iff its t_TYPE
template parameter is bit-wise moveable. There is no way to get this effect using BSLMF_NESTED_TRAIT_DECLARATION
, so we use partial specialization combined with inheritance to "inherit" the trait from t_TYPE
: template <class t_TYPE>
struct MoveableTemplate3
{
t_TYPE d_p;
};
namespace bslmf {
template <class t_TYPE>
struct IsBitwiseMoveable<MoveableTemplate3<t_TYPE> > :
IsBitwiseMoveable<t_TYPE>::type { };
}
Now, we check that the traits are correctly associated by instantiating each class with both bit-wise moveable and non-moveable types and verifying the value of IsBitwiseMoveable<T>value
: int usageExample2()
{
using namespace bslmf;
assert(! IsBitwiseMoveable<
NonMoveableTemplate<NonMoveableClass> >::value);
assert(! IsBitwiseMoveable<
NonMoveableTemplate<MoveableClass1> >::value);
assert( IsBitwiseMoveable<
MoveableTemplate1<NonMoveableClass> >::value);
assert( IsBitwiseMoveable<
MoveableTemplate1<MoveableClass1> >::value);
assert( IsBitwiseMoveable<
MoveableTemplate2<NonMoveableClass> >::value);
assert( IsBitwiseMoveable<
MoveableTemplate2<MoveableClass1> >::value);
assert(! IsBitwiseMoveable<
MoveableTemplate3<NonMoveableClass> >::value);
assert( IsBitwiseMoveable<
MoveableTemplate3<MoveableClass1> >::value);
return 0;
}
}
-
- Example 3: Avoiding False Positives on One-Byte Classes:
- In this example, we define an empty class that has a non-trivial copy constructor that has a global side effect. The side effect should not be omitted, even in a destructive-move situation, so
IsBitwiseMoveable
should be false. However, the heuristic described above would deduce any one-byte class (including an empty class) as bitwise-moveable by default, so we must take specific action to set the trait to false in this (rare) case.
- First, we declare a normal empty class that is bitwise moveable:
namespace BloombergLP {
namespace xyza {
class MoveableEmptyClass
{
};
The class above requires no special treatment. Next, we define an empty class that is not bitwise moveable: class NonMoveableEmptyClass
{
static int d_count;
public:
NonMoveableEmptyClass() { ++d_count; }
NonMoveableEmptyClass(const NonMoveableEmptyClass&) { ++d_count; }
};
int NonMoveableEmptyClass::d_count = 0;
}
Next, we specialize the IsBitwiseMoveable
trait so that NonMoveableEmptyClass
is not incorrectly flagged by trait deduction as having the IsBitwiseMoveable
trait: namespace bslmf {
template <>
struct IsBitwiseMoveable<xyza::NonMoveableEmptyClass> : bsl::false_type
{
};
}
Finally, we show that the first class has the IsBitwiseMoveable
trait and the second class does not: int main()
{
using namespace bslmf;
assert( IsBitwiseMoveable<xyza::MoveableEmptyClass>::value);
assert(! IsBitwiseMoveable<xyza::NonMoveableEmptyClass>::value);
}
}