You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Conception/drake-master/common/reset_on_copy.h

187 lines
7.6 KiB

#pragma once
#include <type_traits>
#include <utility>
namespace drake {
// NOTE(sherm1) to future implementers: if you decide to extend this adapter for
// use with class types, be sure to think carefully about the semantics of copy
// and move and how to explain that to users. Be aware that for class types T,
// the implementation of `T{}` (the default constructor, which may have been
// user-supplied) won't necessarily reset T's members to zero, nor even
// necessarily value-initialize T's members. Also, the "noexcept" reasoning
// below is more than we need with the std::is_scalar<T> restriction, but is
// strictly necessary for class types if you want std::vector to choose move
// construction (content-preserving) over copy construction (resetting).
/// Type wrapper that performs value-initialization on copy construction or
/// assignment.
///
/// Rather than copying the source supplied for copy construction or copy
/// assignment, this wrapper instead value-initializes the destination object.
/// Move assignment and construction preserve contents in the destination as
/// usual, but reset the source to its value-initialized value.
///
/// Only types T that satisfy `std::is_scalar<T>` are currently
/// permitted: integral and floating point types, enums, and pointers.
/// Value initialization means the initialization performed when a variable is
/// constructed with an empty initializer `{}`. For the restricted set of types
/// we support, that just means that numeric types are set to zero and pointer
/// types are set to nullptr. Also, all the methods here are noexcept due to the
/// `std::is_scalar<T>` restriction.
/// See http://en.cppreference.com/w/cpp/language/value_initialization.
///
/// Background:
///
/// It is preferable to use default copy construction for classes whenever
/// possible because it avoids difficult-to-maintain enumeration of member
/// fields in bespoke copy constructors. The presence of fields that must be
/// reset to zero in the copy (counters, for example) prevents use of default
/// copy construction. Similarly, pointers that would be invalid in the copy
/// need to be set to null to avoid stale references. By wrapping those
/// problematic data members in this adapter, default copy construction can
/// continue to be used, with all data members copied properly except the
/// designated ones, which are value-initialized instead. The resetting of the
/// source on move doesn't change semantics since the condition of the source
/// after a move is generally undefined. It is instead opportunistic good
/// hygiene for early detection of bugs, taking advantage of the fact that we
/// know type T can be value-initialized. See reset_after_move for more
/// discussion.
///
/// Example:
///
/// <pre>
/// class Foo {
/// public:
/// DRAKE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN(Foo)
/// Foo() = default;
///
/// private:
/// std::vector<int> items_;
/// reset_on_copy<int> use_count_;
/// };
/// </pre>
///
/// When copying from `Foo`, the new object will contain a copy of `items_`
/// but `use_count_` will be zero. If `Foo` had not used the
/// `reset_on_copy` wrapper, `use_count_` would have been copied also,
/// which we're assuming is not the desired behavior here.
///
/// @warning Even if you initialize a %reset_on_copy member to a non-zero value
/// using an initializer like `reset_on_copy<int> some_member_{5}` it will be
/// _reset_ to zero, not _reinitialized_ to 5 when copied.
///
/// @note Enum types T are permitted, but be aware that they will be reset to
/// zero, regardless of whether 0 is one of the specified enumeration values.
///
/// @tparam T must satisfy `std::is_scalar<T>`.
/// @see reset_after_move
template <typename T>
class reset_on_copy {
public:
static_assert(std::is_scalar_v<T>,
"reset_on_copy<T> is permitted only for integral, "
"floating point, and pointer types T.");
/// Constructs a reset_on_copy<T> with a value-initialized wrapped value.
reset_on_copy() noexcept(std::is_nothrow_default_constructible_v<T>) {}
/// Constructs a %reset_on_copy<T> with a copy of the given value. This is
/// an implicit conversion, so that %reset_on_copy<T> behaves more like
/// the unwrapped type.
// NOLINTNEXTLINE(runtime/explicit)
reset_on_copy(const T& value) noexcept(
std::is_nothrow_copy_constructible_v<T>)
: value_(value) {}
/// Constructs a %reset_on_copy<T> with the given wrapped value, by move
/// construction if possible. This is an implicit conversion, so that
/// %reset_on_copy<T> behaves more like the unwrapped type.
// NOLINTNEXTLINE(runtime/explicit)
reset_on_copy(T&& value) noexcept(
std::is_nothrow_move_constructible_v<T>)
: value_(std::move(value)) {}
/// @name Implements copy/move construction and assignment.
/// These make %reset_on_copy objects CopyConstructible, CopyAssignable,
/// MoveConstructible, and MoveAssignable.
//@{
/// Copy constructor just value-initializes instead; the source is ignored.
reset_on_copy(const reset_on_copy&) noexcept(
std::is_nothrow_default_constructible_v<T>) {}
/// Copy assignment just destructs the contained value and then
/// value-initializes it, _except_ for self-assignment which does nothing.
/// The source argument is otherwise ignored.
reset_on_copy& operator=(const reset_on_copy& source) noexcept(
std::is_nothrow_destructible_v<T> &&
std::is_nothrow_default_constructible_v<T>) {
if (this != &source) destruct_and_reset_value();
return *this;
}
/// Move construction uses T's move constructor, then destructs and
/// value initializes the source.
reset_on_copy(reset_on_copy&& source) noexcept(
std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_destructible_v<T> &&
std::is_nothrow_default_constructible_v<T>)
: value_(std::move(source.value_)) {
source.destruct_and_reset_value();
}
/// Move assignment uses T's move assignment, then destructs and value
/// initializes the source, _except_ for self-assignment which does nothing.
/// The source argument is otherwise ignored.
reset_on_copy& operator=(reset_on_copy&& source) noexcept(
std::is_nothrow_move_assignable_v<T> &&
std::is_nothrow_destructible_v<T> &&
std::is_nothrow_default_constructible_v<T>) {
if (this != &source) {
value_ = std::move(source);
source.destruct_and_reset_value();
}
return *this;
}
//@}
/// @name Implicit conversion operators to make reset_on_copy<T> act
/// as the wrapped type.
//@{
operator T&() noexcept { return value_; }
operator const T&() const noexcept { return value_; }
//@}
/// @name Dereference operators if T is a pointer type.
/// If type T is a pointer, these exist and return the pointed-to object.
/// For non-pointer types these methods are not instantiated.
//@{
template <typename T1 = T>
std::enable_if_t<std::is_pointer_v<T1>, T> operator->() const noexcept {
return value_;
}
template <typename T1 = T>
std::enable_if_t<std::is_pointer_v<T1>,
std::add_lvalue_reference_t<std::remove_pointer_t<T>>>
operator*() const noexcept {
return *value_;
}
//@}
private:
// Invokes T's destructor if there is one, then value-initializes. Note that
// the noexcept code above assumes exactly this implementation. Don't change
// this to `value_ = T{}` which would introduce an additional dependence on
// the behavior of T's copy assignment operator.
void destruct_and_reset_value() {
value_.~T(); // Invokes destructor if there is one.
new (&value_) T{}; // Placement new; no heap activity.
}
T value_{};
};
} // namespace drake