forked from pz4kybsvg/Conception
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.
187 lines
7.6 KiB
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
|