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

Macros

#define BSLMF_ISBITWISEMOVEABLE_NO_SUPPORT_FOR_ARRAY_OF_UNKNOWN_BOUND   1
 

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);// Or new ((void*) p2) t_TYPE(std::move(*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:

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:

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,
{
// Bitwise moveable types can be moved using memcpy
memcpy(static_cast<void *>(to), from, size * sizeof(t_TYPE));
}
template <class t_TYPE>
void destructiveMoveArrayImp(t_TYPE *to,
t_TYPE *from,
int size,
{
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,
}
Definition bslmf_isbitwisemoveable.h:718

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 {
};
} // close namespace bslmf
Definition bdlbb_blob.h:576

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:
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;
#define BSLMF_NESTED_TRAIT_DECLARATION(t_TYPE, t_TRAIT)
Definition bslmf_nestedtraitdeclaration.h:231

Finally, invoke destructiveMoveArray on arrays of all three classes:

enum MoveableEnum { A_VALUE };
int usageExample1()
{
using namespace bslmf;
// First, check the basic operation of 'IsBitwiseMoveable':
// For each of our test classes, allocate an array, construct three
// objects into it, then move it into another array.
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());
destructiveMoveArray(p2, p1, nObj);
// Verify that constructor and destructor were called on each move
assert(2 * nObj == NonMoveableClass::ctorCount());
assert(nObj == NonMoveableClass::dtorCount());
// Verify contents
for (int i = 0; i < nObj; ++i) {
assert(i == p2[i].value());
}
// Destroy and deallocate
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());
destructiveMoveArray(p2, p1, nObj);
// Verify that constructor and destructor were NOT called on each
// move
assert(nObj == MoveableClass1::ctorCount());
assert(0 == MoveableClass1::dtorCount());
// Verify contents
for (int i = 0; i < nObj; ++i) {
assert(i == p2[i].value());
}
// Destroy and deallocate
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());
destructiveMoveArray(p2, p1, nObj);
// Verify that constructor and destructor were NOT called on each
// move
assert(nObj == MoveableClass2::ctorCount());
assert(0 == MoveableClass2::dtorCount());
// Verify contents
for (int i = 0; i < nObj; ++i) {
assert(i == p2[i].value());
}
// Destroy and deallocate
for (int i = 0; i < nObj; ++i) {
p2[i].~MoveableClass2();
}
::operator delete(p1);
::operator delete(p2);
}
return 0;
}
} // close enterprise namespace

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 {
};
} // close namespace bslmf

Third, we define MoveableTemplate2, which uses the BSLMF_NESTED_TRAIT_DECLARATION macro to associate the IsBitwiseMoveable trait with each instantiation:

template <class t_TYPE>
struct MoveableTemplate2
{
t_TYPE *d_p;
BSLMF_NESTED_TRAIT_DECLARATION(MoveableTemplate2,
};

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 { };
} // close namespace bslmf

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;
NonMoveableTemplate<NonMoveableClass> >::value);
NonMoveableTemplate<MoveableClass1> >::value);
MoveableTemplate1<NonMoveableClass> >::value);
MoveableTemplate1<MoveableClass1> >::value);
MoveableTemplate2<NonMoveableClass> >::value);
MoveableTemplate2<MoveableClass1> >::value);
MoveableTemplate3<NonMoveableClass> >::value);
MoveableTemplate3<MoveableClass1> >::value);
return 0;
}
} // close enterprise namespace

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
{
// This class is implicitly moveable by virtue of being only one byte
// in size.
};

The class above requires no special treatment. Next, we define an empty class that is not bitwise moveable:

class NonMoveableEmptyClass
{
// This class is empty, which normally would imply bitwise moveability.
// However, because it has a non-trivial move/copy constructor, it
// should not be bitwise moved.
static int d_count;
public:
NonMoveableEmptyClass() { ++d_count; }
NonMoveableEmptyClass(const NonMoveableEmptyClass&) { ++d_count; }
};
int NonMoveableEmptyClass::d_count = 0;
} // close package namespace

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
{
};
} // close namespace bslmf

Finally, we show that the first class has the IsBitwiseMoveable trait and the second class does not:

int main()
{
using namespace bslmf;
}
} // close enterprise namespace

Macro Definition Documentation

◆ BSLMF_ISBITWISEMOVEABLE_NO_SUPPORT_FOR_ARRAY_OF_UNKNOWN_BOUND

#define BSLMF_ISBITWISEMOVEABLE_NO_SUPPORT_FOR_ARRAY_OF_UNKNOWN_BOUND   1