C + + message bus Mozart: optional

Posted by Deadmeat on Sat, 22 Jan 2022 06:05:04 +0100

optional principle

Mozart's optional is also a container, which is responsible for managing an optional holding value. This holding value can exist or not exist. File git address: https://github.com/libmozart/foundation/blob/master/mozart%2B%2B/mpp_foundation/optional.hpp

Implementation principle: in addition to containing the value, the data held by optional also adds a boolean flag to mark whether the current optional really exists. Therefore, the optional size is usually one byte larger than the contained object. See the definition of the internal data structure of optional:

std::array<unsigned char, 1 + sizeof(T)> _memory{0};

The first byte memory[0] stores the state of the holding value; If memory[0]==true, it indicates that there is a data object for optional, and memory[0] == false indicates that there is no data object.

memory[1,..., 1+sizeof(T)] other than the first byte stores the original data object.

optional construction

optional constructors include default constructors, empty constructors, implicit (mobile) constructors, list initialization constructors, multi argument initialization constructors, etc.

Default construction

optional() = default;

optional null construction

The optional null construction is only used for the generation of null objects. The optional null object is only an identification description, indicating that the current optional does not contain any objects.

// Define an optional empty object
struct optional_none_t {};
static constexpr optional_none_t none {};

optional the corresponding empty constructor is:

optional(optional_none_t) : optional() 
{}

optional implicit construction

Implicit construction realizes the generation of optional objects based on the original objects. There are two kinds of implicit Construction: one is implicit construction based on left value, and the other is mobile implicit construction based on right value.

Implicit construction based on lvalue:

optional(const T &t) 
{
    new(_memory.data() + 1) T(t);
    _memory[0] = static_cast<unsigned char>(true);
}

Moving implicit construction based on right value

optional(T &&t) 
{
    new(_memory.data() + 1) T(std::forward<T>(t));
    _memory[0] = static_cast<unsigned char>(true);
}

option copy construction

The copy structure of optional includes left value copy structure and right value copy structure.

Lvalue copy

Lvalue copy construction is a copy construction method supported by C++98 in the traditional sense. It is defined as follows:

optional(const optional<T> &other)
{
    if (other.has_value())
    {
        new(_memory.data() + 1) T(other.get());
        _memory[0] = static_cast<unsigned char>(true);
    }
}

Right value copy

The right value copy is a copy structure based on mobile semantics supported by C++11. Its location is as follows:

// Implement the current object_ memory and other_ memory data exchange
void swap(optional<T> &&other) 
{
    std::swap(this->_memory, other._memory);
}

optional(optional<T> &&other) noexcept 
{
    swap(other);
}

optional forwarding structure

Other construction methods of optional are implemented by some mediation forwarding. Let's first introduce some's in_place matching guidance mechanism, and then introduce some mediation forwarding construction mechanism.

in_place

in_ The place mechanism is actually a function definition matching mechanism.

#define mpp_in_place_type_t(T)  mpp_impl::in_place_t(&)(mpp_impl::in_place_type_tag<T>)

template <typename T>
struct in_place_type_tag 
{
};

struct in_place_t 
{
};

Macro mpp_in_place_type_t is a function declaration definition, and the input parameter of this function is mpp_impl::in_place_type_tag, the return value is in_place_t.

template <typename T>
inline in_place_t in_place(in_place_type_tag<T> = in_place_type_tag<T>()) 
{
    return in_place_t{};
}

inline in_ place_ t in_ The definition of the place (in_place_type_tag = in_place_type_tag()) function satisfies the macro mpp_in_place_type_t declares a matching implementation of the function.

some forwarding mechanism

In short, some forwarding is to realize the construction of optional objects through the some function provided by the optional module. There are three kinds of some functions provided by the optional module: single object some forwarding, input parameter list forwarding and list initialization forwarding.

Single object some

Optional < typename STD:: decay:: Type > some (t &&v) implements the initialization of single object right value assignment structure.

template <typename T>
constexpr optional<typename std::decay<T>::type> some(T &&v)
{
    return optional<typename std::decay<T>::type>(std::forward<T>(v));
}

The calling method is:

auto optionalInt = mpp::some(int(1));

This definition will call the some forwarding initialization constructor.

Input parameter list form some

The assignment of this forwarding construction mode adapts to the standard old initialization mode of c++98. The calling mode is:

auto optionalVect98 = mpp::some<std::vector<int>>(1, 2, 3, 4, 5)));

some forwarding constructor definition:

template <typename T, typename... Args>
constexpr optional<T> some(Args &&... args)
{
	return optional<T>(mpp_impl::in_place, std::forward<Args>(args)...);
}

mpp_impl::in_place is a function object. Through this function object, the compiler will match the optional (mpp_in_place_type_t (T), args & &... args) constructor of optional.

template <typename... Args>
constexpr explicit optional(mpp_in_place_type_t(T), Args &&... args)
: optional(T{std::forward<Args>(args)...})
{}

List initialization some

The assignment of this forwarding construction mode adapts to the new initialization mode of c++11 standard. The calling mode is:

auto optionalVect11  = mpp::some<decltype(v)>({ 1, 2, 3, 4, 5 }));

some forwarding constructor definition:

template <typename T, typename U, typename... Args>
constexpr optional<T> some(std::initializer_list<U> il, Args &&... args)
{
    return optional<T>(mpp_impl::in_place, il, std::forward<Args>(args)...);
}

mpp_impl::in_place is a function object. Through this function object, the compiler will match the optional (mpp_in_place_type_t (T), STD:: initializer of optional_ List IL, args & &... args) constructor.

template <typename U, typename... Args>
constexpr explicit optional(mpp_in_place_type_t(T), std::initializer_list<U> il, Args &&... args)
: optional(T{il, std::forward<Args>(args)...}) 
{}

optional deconstruction

The optional destructor realizes the release and recovery of the optional data, and realizes the recovery of the resources managed by the object by positioning the destructor.

T *ptr() 
{
    return has_value() ? reinterpret_cast<T *>(_memory.data() + 1) : nullptr;
}

const T *ptr() const
{
    return has_value() ? reinterpret_cast<const T *>(_memory.data() + 1) : nullptr;
}

~optional()
{
    if (has_value()) 
    {
        ptr()->~T();
        _memory[0] = static_cast<unsigned char>(false);
    }
}

The destructor ~ optional() obtains the pointer of the held object through the ptr() function, and then recycles the resources by positioning the destructor.

What needs to be emphasized here is: why do you use location destruct instead of delete ptr()? The object pointed to by PTR is on the function call stack, and the corresponding address is_ memory.data() + 1, not heap. Therefore, the recycling here can only recycle data, not exchange objects to the operating system, and the location destructor is specifically designed to solve this problem, which is why delete ptr() cannot be called directly to recycle resources.

optional assignment

Similarly, there are two kinds of assignment: left value assignment and right value assignment.

Lvalue assignment

Lvalue assignment is performed through std::swap data exchange.

void swap(optional<T> &other) 
{
    std::swap(this->_memory, other._memory);
}

optional &operator=(const optional<T> &other) 
{
    if (this == &other)
    {
        return *this;
    }

    optional<T> t(other);
    swap(t);
    return *this;
}

Right value assignment

The right value is assigned through std::swap data exchange.

void swap(optional<T> &&other) 
{
    std::swap(this->_memory, other._memory);
}

optional &operator=(optional<T> &&other) noexcept
{
    if (this == &other)
    {
        return *this;
    }

    swap(other);
    return *this;
}

get_or

Get original get_or, which can be divided into const version and sub const version according to whether the calling object is const object. The definitions are as follows:

// Non const version get_or
template <typename U>
std::enable_if_t<std::is_same<T, U>::value, T> &get_or(U &&o)
{
    if (ptr() == nullptr)
    {
        return o;
    }
    return *ptr();
}

// const version get_or
template <typename U>
const std::enable_if_t<std::is_same<T, U>::value, T> &get_or(U &&o) const
{
    if (ptr() == nullptr) 
    {
        return o;
    }
    return *ptr();
}

std::enable_ if_ t<std::is_ Same < T, U >:: value, t > is used to restrict that U and t must be the same type of data, otherwise a compilation error will be reported.

apply/apply_or

Apply and apply_or and get_ There are similarities and differences in the implementation of or. You can analyze it by yourself. The definition is posted here:

void apply(const std::function<void(T &)> &consumer)
{
    if (ptr() != nullptr)
    {
        consumer(get());
    }
}

void apply(const std::function<void(const T &)> &consumer) const 
{
    if (ptr() != nullptr) 
    {
        consumer(get());
    }
}

template <typename R>
R apply_or(const R &r, const std::function<R(T &)> &consumer) 
{
    if (ptr() != nullptr) 
    {
        return consumer(get());
    }
    return r;
}

template <typename R>
R apply_or(const R &r, const std::function<R(const T &)> &consumer) const 
{
    if (ptr() != nullptr) 
    {
        return consumer(get());
    }
    return r;
}

summary

This paper introduces the data storage and interface of the optional class in detail. I hope it can help you.

Topics: C++ Programming