96d9846b25
Reviewers: mislav.bradac Reviewed By: mislav.bradac Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D905
105 lines
4.1 KiB
C++
105 lines
4.1 KiB
C++
#pragma once
|
|
|
|
#include <memory>
|
|
|
|
namespace utils {
|
|
|
|
/**
|
|
* Helper class for implementing copy-on-write member variables. Memory
|
|
* management is automatic via shared-ptr: it is allowed for the original
|
|
* owner to expire, the content will remain valid in copies.
|
|
*
|
|
* This class is generally not thread-safe. However, it is intended for use in
|
|
* Memgraph's MVCC which is concurrent, but with specific guarantees that also
|
|
* make it possible to use this. These guarantees are:
|
|
* 1. Writing can only happen when there are no copies (no parallel reads). An
|
|
* implication of this is that an obtained copy is immutable, even though in
|
|
* general CopyOnWrite does not guarantee this (generally the owner's
|
|
* modificatins will be visible to the copies).
|
|
* 2. Copies are not created in parallel. MVCC guarantees this with
|
|
* record-locking, but should be in fact legal in CopyOnWrite.
|
|
*
|
|
* @tparam TElement - type of content. Must be copy-constructable.
|
|
*/
|
|
template <typename TElement>
|
|
class CopyOnWrite {
|
|
public:
|
|
/**
|
|
* Creates a new CopyOnWrite that owns it's element.
|
|
*
|
|
* @param args - Arguments forwarded to the TElement constructor.
|
|
* @tparam TArgs - Argument types.
|
|
*/
|
|
template <typename... TArgs>
|
|
CopyOnWrite(TArgs &&... args) {
|
|
TElement *new_element = new TElement(std::forward<TArgs>(args)...);
|
|
element_ptr_.reset(new_element);
|
|
is_owner_ = true;
|
|
}
|
|
|
|
/** Creates a copy of the given CopyOnWrite object that does not copy the
|
|
* element and does not assume ownership over it. */
|
|
CopyOnWrite(const CopyOnWrite &other)
|
|
: element_ptr_{other.element_ptr_}, is_owner_{false} {}
|
|
|
|
/** Creates a copy of the given CopyOnWrite object that does not copy the
|
|
* element and does not assume ownership over it. This is a non-const
|
|
* reference accepting copy constructor. The hack is necessary to prevent the
|
|
* variadic constructor from being a better match for non-const CopyOnWrite
|
|
* argument. */
|
|
CopyOnWrite(CopyOnWrite &other)
|
|
: CopyOnWrite(const_cast<const CopyOnWrite &>(other)) {}
|
|
|
|
/** Creates a copy of the given temporary CyopOnWrite object. If the temporary
|
|
* is owner then ownership is transferred to this CopyOnWrite. Otherwise this
|
|
* CopyOnWrite does not become the owner. */
|
|
CopyOnWrite(CopyOnWrite &&other) = default;
|
|
|
|
/** Copy assignment of another CopyOnWrite. Does not transfer ownership (this
|
|
* CopyOnWrite is not the owner). */
|
|
CopyOnWrite &operator=(const CopyOnWrite &other) {
|
|
element_ptr_ = other.element_ptr_;
|
|
is_owner_ = false;
|
|
return *this;
|
|
}
|
|
|
|
/** Copy assignment of a temporary CopyOnWrite. If the temporary is owner then
|
|
* ownerships is transferred to this CopyOnWrite. Otherwise this CopyOnWrite
|
|
* does not become the owner. */
|
|
CopyOnWrite &operator=(CopyOnWrite &&other) = default;
|
|
|
|
// All the dereferencing operators are overloaded to return a const element
|
|
// reference. There is no differentiation between const and non-const member
|
|
// function behavior because an implicit copy creation on non-const
|
|
// dereferencing would most likely result in excessive copying. For that
|
|
// reason
|
|
// an explicit call to the `Write` function is required to obtain a non-const
|
|
// reference to element.
|
|
const TElement &operator*() { return *element_ptr_; }
|
|
const TElement &operator*() const { return *element_ptr_; }
|
|
const TElement &get() { return *element_ptr_; }
|
|
const TElement &get() const { return *element_ptr_; }
|
|
const TElement *operator->() { return element_ptr_.get(); }
|
|
const TElement *operator->() const { return element_ptr_.get(); }
|
|
|
|
/** Indicates if this CopyOnWrite object is the owner of it's element. */
|
|
bool is_owner() const { return is_owner_; };
|
|
|
|
/**
|
|
* If this CopyOnWrite is the owner of it's element, a non-const reference to
|
|
* is returned. If this CopyOnWrite is not the owner, then the element is
|
|
* copied and this CopyOnWrite becomes the owner.
|
|
*/
|
|
TElement &Write() {
|
|
if (is_owner_) return *element_ptr_;
|
|
element_ptr_ = std::shared_ptr<TElement>(new TElement(*element_ptr_));
|
|
is_owner_ = true;
|
|
return *element_ptr_;
|
|
};
|
|
|
|
private:
|
|
std::shared_ptr<TElement> element_ptr_;
|
|
bool is_owner_{false};
|
|
};
|
|
} // namespace utils
|