Provide a wrapper that asserts a noexcept move constructor.
More...
Detailed Description
- Outline
-
-
- Purpose:
- Provide a wrapper that asserts a noexcept move constructor.
-
- Classes:
-
- See also:
- Component bslalg_nothrowmovablewrapper
-
- Description:
- This component provides a utility struct,
bslalg::NothrowMovableUtil
, for managing bslalg_nothrowmovablewrapper
objects. It provides a namespace for static functions wrap
and unwrap
with a uniform interface such that unwrapping an object that is not wrapped or wrapping an object that is already wrapped are no-ops. This utility struct also provides type traits for determining whether a type is wrapped and for deducing the type of the wrapped and unwrapped object.
-
- Usage:
-
- Example:
- In this example, we define a class template,
CountedType<TYPE>
, a wrapper around TYPE
that counts the number of extant CountedType
objects. We begin by defining the static count member along with the single value member: template <class TYPE>
class CountedType {
static int s_count;
TYPE d_value;
Because of externally-imposed requirements, the move constructor for CountedType
must provide the strong guarantee; i.e., if the move constructor of TYPE
throws an exception, then the moved-from CountedType
object must be left unchanged. To support this requirement, we next define a private static function, MoveIfNoexcept
, similar to the standard std::move_if_noexcept
, that returns a movable reference if its argument is no-throw move constructible and a const lvalue reference otherwise: We next finish out the class definition with a constructor, copy constructor, move constructor, destructor, and member functions to retrieve the count and value: public:
static int count() { return s_count; }
CountedType(const TYPE& val);
CountedType(const CountedType& original);
CountedType(bslmf::MovableRef<CountedType> original);
~CountedType() { --s_count; }
TYPE& value() { return d_value; }
const TYPE& value() const { return d_value; }
};
Next, we implement MoveIfNoexcept
, which calls move
on its argument, allowing it to convert back to an lvalue if the return type is an lvalue reference: Next, we implement the value constructor and copy constructor, which simply copy their argument into the d_value
data members and increment the count: template <class TYPE>
CountedType<TYPE>::CountedType(const TYPE& val) : d_value(val)
{
++s_count;
}
template <class TYPE>
CountedType<TYPE>::CountedType(const CountedType& original)
: d_value(original.d_value)
{
++s_count;
}
We're now ready implement the move constructor. Logically, we would simply move the value from original
into the d_value
member of *this
, but an exception thrown by TYPE
s move constructor would leave 'original
in a (valid but) unspecified state, violating the strong guarantee. Instead, we move the value only if we know that the move will succeed; otherwise, we copy it. This behavior is facilitated by the MoveIfNoexcept
function defined above: template <class TYPE>
CountedType<TYPE>::CountedType(bslmf::MovableRef<CountedType> original)
: d_value(
MoveIfNoexcept(bslmf::MovableRefUtil::access(original).d_value))
{
++s_count;
}
Finally, we define the s_count
member to complete the class implementation: template <class TYPE>
int CountedType<TYPE>::s_count = 0;
To test the CountedType
class template, assume a simple client type, SomeType
that makes it easy to detect if it was move constructed. SomeType
holds an int
value that is set to -1 when it is moved from, as shown here: class SomeType {
int d_value;
public:
SomeType(int v = 0) : d_value(v) { }
SomeType(const SomeType& original) : d_value(original.d_value) { }
SomeType(bslmf::MovableRef<SomeType> original)
: d_value(bslmf::MovableRefUtil::access(original).d_value)
{ bslmf::MovableRefUtil::access(original).d_value = -1; }
int value() const { return d_value; }
};
Notice that SomeType
neglected to declare its move constructor as noexcept
. This might be an oversight or it could be an old class that predates both noexcept
and the bsl::is_nothrow_move_constructible
trait. It is even be possible that the move constructor might throw (though, of course, it doesn't in this simplified example). Regardless, the effect is that move-constructing a CountedType<SomeType>
will result in the move constructor actually performing a copy: void main()
{
CountedType<SomeType> obj1(1);
CountedType<SomeType> obj2(bslmf::MovableRefUtil::move(obj1));
assert(1 == obj1.value().value());
assert(1 == obj2.value().value());
For the purpose of this example, we can be sure that SomeThing
will not throw on move, at least not in our application. In order to obtain the expected move optimization, we next wrap our 'SomeType in a bslalg::NothrowMovableWrapper
: CountedType<bslalg::NothrowMovableWrapper<SomeType> >
obj3(SomeType(3));
CountedType<bslalg::NothrowMovableWrapper<SomeType> >
obj4(bslmf::MovableRefUtil::move(obj3));
assert(-1 == obj3.value().unwrap().value());
assert(3 == obj4.value().unwrap().value());
}
- Note that, in the last two lines of
main
, we must call unwrap
in order to access the SomeType
object inside of the NothrowMovableWrapper
. This is one situation where it would be attractive to have an overloadable "operator dot" so that both CountedThing
and NothrowMovableWrapper
could be transparent proxy types. C++ does not have overloadable operator dot, but we can create a CountedType
that is more intelligent about the existence of NothrowMovableWrapper
and automatically unwraps values for the user's convenience.
- Rather than starting from scratch, we'll build our new counted type,
CountedType2
on CountedType
. We start be defining a single data member of type CountedType
: template <class TYPE>
class CountedType2 {
CountedType<TYPE> d_data;
Next, for convenience, we add a public data type, ValueType
for the value stored within CountedType2
. However, rather than defining ValueType
as simply TYPE
, we want to know if it is an instantiation of NothrowMovableWrapper<TP>
. If it is, we want a type that represents the unwrapped TP
rather than the full TYPE
. For this type transformation, we turn to the type traits defined in bslalg::NothrowMovableUtil
: Note that the UnwrappedType
metafunction has no affect if TYPE
is not wrapped.
- Next, we declare (and define) the class functions, constructors, and destructor, simply forwarding to the corresponding
CountedType
function, constructor, or destructor:
static int count() { return CountedType<TYPE>::count(); }
CountedType2(const TYPE& val) : d_data(val) { }
CountedType2(const CountedType2& original)
: d_data(original.d_data) { }
CountedType2(bslmf::MovableRef<CountedType2> original)
: d_data(bslmf::MovableRefUtil::move(
bslmf::MovableRefUtil::access(original).d_data)) { }
Finally, we implement the value()
members such that the returned values do not need to be unwrapped. As in the case of the UnwrappedType
metafunction, the unwrap()
function in NothrowMovableUtil
handles both wrapped and unwrapped arguments, unwrapping the latter and returning an unmodified reference to the former: Note the alternative code for these members: A NothrowMovableWrapper<TP>
object is implicitly convertible to TP&
, so if TYPE
is a NothrowMovableWrapper
, the simple return statement will implicitly unwrap it.
- Using a similar example for
CountedType2
as we used for CountedType
, we see that the usage of CountedType2
with and without NothrowMovableWrapper
is the same: void main()
{
CountedType2<SomeType> obj1(1);
CountedType2<SomeType> obj2(bslmf::MovableRefUtil::move(obj1));
assert(1 == obj1.value().value());
assert(1 == obj2.value().value());
CountedType2<bslalg::NothrowMovableWrapper<SomeType> >
obj3(SomeType(3));
CountedType2<bslalg::NothrowMovableWrapper<SomeType> >
obj4(bslmf::MovableRefUtil::move(obj3));
assert(-1 == obj3.value().value());
assert(3 == obj4.value().value());
}