Boost logo

Boost :

Subject: Re: [boost] [optional] Specializing optional to save space
From: David Stone (david_at_[hidden])
Date: 2015-09-27 22:47:07


class string {
    string() = default;
    string & operator=(string && other) noexcept {
        delete m_data;
        m_data = other.m_data;
        other.m_data = nullptr;

        m_capacity = other.m_capacity;
        other.m_capacity = 0;

        m_size = other.m_size;
        other.m_size = 0;

        return *this;
    }
private:
    friend optional<string>;

    char * m_data = nullptr;
    size_t m_capacity = 0;
    size_t m_size = 0;
};

template<>
class optional_storage<string> {
    optional_storage() noexcept {
        reset();
    }
    template<typename... Args>
    explicit optional_storage(in_place_t, Args && ... args):
        m_value(std::forward<Args>(args)...) {
    }

    template<typename... Args>
    void emplace(Args && ... args) {
        m_value = string(std::forward<Args>(args)...);
    }

    void reset() noexcept {
        delete m_value.m_data;
        m_value.m_data = nullptr;
        m_value.m_size = uninitialized_size();
    }

    bool is_initialized() const noexcept {
        return m_value.m_size == uninitialized_size();
    }
    auto && value() const & noexcept {
        return m_value;
    }
    auto && value() & noexcept {
        return m_value;
    }
    auto && value() && noexcept {
        return std::move(m_value);
    }
private:
    static constexpr auto uninitialized_size() noexcept {
        return std::numeric_limits<std::size_t>::max();
    }
    string m_value;
};

Other than looking at its size, I do not believe that the user can tell
whether we have done this or have used the default optional. It is
undefined behavior to call operator*() on an uninitialized optional, and
every other way of getting at the value first checks is_initialized(), so
no one can ever have a reference to a string that has been set to the
uninitialized state.

I included the implementation of the move assignment operator to show that
it does not change at all with this optimization. It can just blindly
delete what it used to have and do assignments as usual. emplace can call
the move assignment operator after constructing a temporary, because that
is identical to destruct + construct. I would expect the optimizer would
compile that down to the same code, in fact, as an explicit destructor and
a placement new.

reset has to set the pointer to nullptr when uninitializing to make it safe
to blindly call delete again.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk