Commit aee597a7 authored by Dominik Charousset's avatar Dominik Charousset

Give COW pointer const-by-default semantics

The current design of intrusive_cow_ptr with const-overloaded member
functions makes it very easy to accidentially create copies. Further,
`intrusive_cow_ptr` does no longer work with forward-declared types,
since it calls T::copy. The new design adds `intrusive_cow_ptr_unshare`
as a customization point to work with forward-declared types, adds
`make_copy_on_write` as complement to `make_shared`, and requires users
to explicitly call `unshared()` or `unshared_ptr()` to get mutable
access to the managed object.
parent 0df7fab1
......@@ -46,7 +46,7 @@ public:
// -- overridden observers of message_data -----------------------------------
cow_ptr copy() const override;
concatenated_tuple* copy() const override;
// -- overridden modifiers of type_erased_tuple ------------------------------
......
......@@ -50,7 +50,7 @@ public:
// -- overridden observers of message_data -----------------------------------
cow_ptr copy() const override;
message_data* copy() const override;
// -- overridden modifiers of type_erased_tuple ------------------------------
......
......@@ -45,7 +45,7 @@ public:
// -- overridden observers of message_data -----------------------------------
cow_ptr copy() const override;
dynamic_message_data* copy() const override;
// -- overridden modifiers of type_erased_tuple ------------------------------
......@@ -84,6 +84,12 @@ private:
uint32_t type_token_;
};
void intrusive_ptr_add_ref(const dynamic_message_data*);
void intrusive_ptr_release(const dynamic_message_data*);
dynamic_message_data* intrusive_cow_ptr_unshare(dynamic_message_data*&);
} // namespace detail
} // namespace caf
......@@ -46,7 +46,7 @@ public:
// -- overridden observers of message_data -----------------------------------
cow_ptr copy() const override;
merged_tuple* copy() const override;
// -- overridden modifiers of type_erased_tuple ------------------------------
......
......@@ -50,7 +50,7 @@ public:
// -- pure virtual observers -------------------------------------------------
virtual cow_ptr copy() const = 0;
virtual message_data* copy() const = 0;
// -- observers --------------------------------------------------------------
......
......@@ -220,8 +220,8 @@ public:
using super::copy;
message_data::cow_ptr copy() const override {
return message_data::cow_ptr(new tuple_vals(*this), false);
message_data* copy() const override {
return new tuple_vals(*this);
}
};
......
......@@ -248,9 +248,14 @@ class message_data;
class private_thread;
class uri_impl;
void intrusive_ptr_add_ref(const uri_impl* p);
void intrusive_ptr_release(const uri_impl* p);
// enable intrusive_ptr<uri_impl> with forward declaration only
void intrusive_ptr_add_ref(const uri_impl*);
void intrusive_ptr_release(const uri_impl*);
// enable intrusive_cow_ptr<dynamic_message_data> with forward declaration only
void intrusive_ptr_add_ref(const dynamic_message_data*);
void intrusive_ptr_release(const dynamic_message_data*);
dynamic_message_data* intrusive_cow_ptr_unshare(dynamic_message_data*&);
} // namespace detail
......
......@@ -19,11 +19,29 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "caf/intrusive_ptr.hpp"
namespace caf {
/// Default implementation for unsharing values.
/// @relates intrusive_cow_ptr
template <class T>
T* default_intrusive_cow_ptr_unshare(T*& ptr) {
if (!ptr->unique())
ptr = ptr->copy();
return ptr;
}
/// Customization point for allowing intrusive_cow_ptr<T> with a forward
/// declared T.
/// @relates intrusive_cow_ptr
template <class T>
T* intrusive_cow_ptr_unshare(T*& ptr) {
return default_intrusive_cow_ptr_unshare(ptr);
}
/// An intrusive, reference counting smart pointer implementation with
/// copy-on-write optimization.
/// @relates ref_counted
......@@ -49,7 +67,7 @@ public:
using counting_pointer = intrusive_ptr<T>;
// -- constructors, destructors, and assignment operators ------------------
// -- constructors, destructors, and assignment operators --------------------
intrusive_cow_ptr() noexcept = default;
......@@ -57,6 +75,12 @@ public:
intrusive_cow_ptr(const intrusive_cow_ptr&) noexcept = default;
template <class Y>
intrusive_cow_ptr(intrusive_cow_ptr<Y> other) noexcept
: ptr_(other.detach(), false) {
// nop
}
explicit intrusive_cow_ptr(counting_pointer p) noexcept : ptr_(std::move(p)) {
// nop
}
......@@ -75,14 +99,14 @@ public:
return *this;
}
// -- comparison -----------------------------------------------------------
// -- comparison -------------------------------------------------------------
ptrdiff_t compare(std::nullptr_t) const noexcept {
return reinterpret_cast<ptrdiff_t>(get());
}
ptrdiff_t compare(const_pointer ptr) const noexcept {
return static_cast<ptrdiff_t>(get() - ptr);
return reinterpret_cast<intptr_t>(get()) - reinterpret_cast<intptr_t>(ptr);
}
ptrdiff_t compare(const counting_pointer& other) const noexcept {
......@@ -93,7 +117,7 @@ public:
return compare(other.get());
}
// -- modifiers ------------------------------------------------------------
// -- modifiers --------------------------------------------------------------
/// Swaps the managed object with `other`.
void swap(intrusive_cow_ptr& other) noexcept {
......@@ -121,30 +145,20 @@ public:
/// count of exactly 1.
void unshare() {
if (ptr_ != nullptr)
static_cast<void>(get_unshared());
static_cast<void>(unshared());
}
/// Returns a mutable pointer to the managed object.
pointer operator->() {
return get_unshared();
pointer unshared_ptr() {
return intrusive_cow_ptr_unshare(ptr_.ptr_);
}
/// Returns a mutable reference to the managed object.
reference operator*() {
return *get_unshared();
reference unshared() {
return *unshared_ptr();
}
/// Returns a mutable pointer to the managed object.
pointer get_unshared() {
auto p = ptr_.get();
if (!p->unique()) {
copy_reset(p->copy());
return ptr_.get();
}
return p;
}
// -- observers ------------------------------------------------------------
// -- observers --------------------------------------------------------------
/// Returns a read-only pointer to the managed object.
const_pointer get() const noexcept {
......@@ -171,21 +185,6 @@ public:
}
private:
// -- allow T::copy to return raw, intrusive, or COW pointer -----------------
void copy_reset(pointer x) {
// Reference counted objects start with a reference count of 1.
reset(x, false);
}
void copy_reset(counting_pointer x) {
ptr_.swap(x);
}
void copy_reset(intrusive_cow_ptr x) {
swap(x);
}
// -- member vairables -------------------------------------------------------
counting_pointer ptr_;
......
......@@ -27,6 +27,7 @@
#include "caf/detail/append_hex.hpp"
#include "caf/detail/comparable.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
namespace caf {
......@@ -35,15 +36,30 @@ namespace caf {
template <class T>
class intrusive_ptr {
public:
// -- friends ----------------------------------------------------------------
template <class>
friend class intrusive_cow_ptr;
// -- member types -----------------------------------------------------------
using pointer = T*;
using const_pointer = const T*;
using element_type = T;
using reference = T&;
using const_reference = const T&;
// -- constants --------------------------------------------------------------
// tell actor_cast which semantic this type uses
static constexpr bool has_weak_ptr_semantics = false;
// -- constructors, destructors, and assignment operators --------------------
constexpr intrusive_ptr() noexcept : ptr_(nullptr) {
// nop
}
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/intrusive_cow_ptr.hpp"
namespace caf {
/// Constructs an object of type `T` in an `intrusive_cow_ptr`.
/// @relates ref_counted
/// @relates intrusive_cow_ptr
template <class T, class... Ts>
intrusive_cow_ptr<T> make_copy_on_write(Ts&&... xs) {
return intrusive_cow_ptr<T>(new T(std::forward<Ts>(xs)...), false);
}
} // namespace caf
......@@ -258,7 +258,7 @@ public:
/// @pre `!empty()`
inline type_erased_tuple& content() {
CAF_ASSERT(vals_ != nullptr);
return *vals_;
return vals_.unshared();
}
inline const type_erased_tuple& content() const {
......
......@@ -133,11 +133,7 @@ private:
message_builder& emplace(type_erased_value_ptr);
detail::dynamic_message_data* data();
const detail::dynamic_message_data* data() const;
intrusive_ptr<ref_counted> data_; // hide dynamic_message_data implementation
intrusive_cow_ptr<detail::dynamic_message_data> data_;
};
} // namespace caf
......
......@@ -53,8 +53,8 @@ auto concatenated_tuple::make(std::initializer_list<cow_ptr> xs) -> cow_ptr {
return cow_ptr{make_counted<concatenated_tuple>(xs)};
}
message_data::cow_ptr concatenated_tuple::copy() const {
return cow_ptr(new concatenated_tuple(*this), false);
concatenated_tuple* concatenated_tuple::copy() const {
return new concatenated_tuple(*this);
}
void* concatenated_tuple::get_mutable(size_t pos) {
......@@ -114,7 +114,7 @@ std::pair<message_data*, size_t> concatenated_tuple::select(size_t pos) {
if (idx >= s)
idx -= s;
else
return {m.get_unshared(), idx};
return {m.unshared_ptr(), idx};
}
CAF_RAISE_ERROR(std::out_of_range, "concatenated_tuple::select out of range");
}
......
......@@ -49,18 +49,18 @@ decorated_tuple::cow_ptr decorated_tuple::make(cow_ptr d, vector_type v) {
return decorated_tuple::cow_ptr{res};
}
message_data::cow_ptr decorated_tuple::copy() const {
return cow_ptr(new decorated_tuple(*this), false);
message_data* decorated_tuple::copy() const {
return new decorated_tuple(*this);
}
void* decorated_tuple::get_mutable(size_t pos) {
CAF_ASSERT(pos < size());
return decorated_->get_mutable(mapping_[pos]);
return decorated_.unshared().get_mutable(mapping_[pos]);
}
error decorated_tuple::load(size_t pos, deserializer& source) {
CAF_ASSERT(pos < size());
return decorated_->load(mapping_[pos], source);
return decorated_.unshared().load(mapping_[pos], source);
}
size_t decorated_tuple::size() const noexcept {
......
......@@ -19,6 +19,7 @@
#include "caf/detail/dynamic_message_data.hpp"
#include "caf/error.hpp"
#include "caf/intrusive_cow_ptr.hpp"
#include "caf/make_counted.hpp"
namespace caf {
......@@ -48,8 +49,8 @@ dynamic_message_data::~dynamic_message_data() {
// nop
}
message_data::cow_ptr dynamic_message_data::copy() const {
return message_data::cow_ptr{make_counted<dynamic_message_data>(*this)};
dynamic_message_data* dynamic_message_data::copy() const {
return new dynamic_message_data(*this);
}
void* dynamic_message_data::get_mutable(size_t pos) {
......@@ -109,5 +110,17 @@ void dynamic_message_data::add_to_type_token(uint16_t typenr) {
type_token_ = (type_token_ << 6) | typenr;
}
void intrusive_ptr_add_ref(const dynamic_message_data* ptr) {
intrusive_ptr_add_ref(static_cast<const ref_counted*>(ptr));
}
void intrusive_ptr_release(const dynamic_message_data* ptr) {
intrusive_ptr_release(static_cast<const ref_counted*>(ptr));
}
dynamic_message_data* intrusive_cow_ptr_unshare(dynamic_message_data*& ptr) {
return default_intrusive_cow_ptr_unshare(ptr);
}
} // namespace detail
} // namespace caf
......@@ -53,20 +53,20 @@ merged_tuple::merged_tuple(data_type xs, mapping_type ys)
}
}
merged_tuple::cow_ptr merged_tuple::copy() const {
return cow_ptr{make_counted<merged_tuple>(data_, mapping_)};
merged_tuple* merged_tuple::copy() const {
return new merged_tuple(data_, mapping_);
}
void* merged_tuple::get_mutable(size_t pos) {
CAF_ASSERT(pos < mapping_.size());
auto& p = mapping_[pos];
return data_[p.first]->get_mutable(p.second);
return data_[p.first].unshared().get_mutable(p.second);
}
error merged_tuple::load(size_t pos, deserializer& source) {
CAF_ASSERT(pos < mapping_.size());
auto& p = mapping_[pos];
return data_[p.first]->load(p.second, source);
return data_[p.first].unshared().load(p.second, source);
}
size_t merged_tuple::size() const noexcept {
......
......@@ -60,13 +60,13 @@ message::~message() {
void* message::get_mutable(size_t p) {
CAF_ASSERT(vals_ != nullptr);
return vals_->get_mutable(p);
return vals_.unshared().get_mutable(p);
}
error message::load(size_t pos, deserializer& source) {
CAF_ASSERT(vals_ != nullptr);
return vals_->load(pos, source);
return vals_.unshared().load(pos, source);
}
size_t message::size() const noexcept {
......
......@@ -16,12 +16,13 @@
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include <vector>
#include "caf/message_builder.hpp"
#include "caf/message_handler.hpp"
#include <vector>
#include "caf/detail/dynamic_message_data.hpp"
#include "caf/make_copy_on_write.hpp"
#include "caf/message_handler.hpp"
namespace caf {
......@@ -37,15 +38,18 @@ void message_builder::init() {
// this should really be done by delegating
// constructors, but we want to support
// some compilers without that feature...
data_ = make_counted<detail::dynamic_message_data>();
data_ = make_copy_on_write<detail::dynamic_message_data>();
}
void message_builder::clear() {
data()->clear();
if (data_->unique())
data_.unshared().clear();
else
init();
}
size_t message_builder::size() const {
return data()->size();
return data_->size();
}
bool message_builder::empty() const {
......@@ -53,23 +57,16 @@ bool message_builder::empty() const {
}
message_builder& message_builder::emplace(type_erased_value_ptr x) {
data()->append(std::move(x));
data_.unshared().append(std::move(x));
return *this;
}
message message_builder::to_message() const {
// this const_cast is safe, because the message is
// guaranteed to detach its data before modifying it
detail::message_data::cow_ptr ptr;
ptr.reset(const_cast<detail::dynamic_message_data*>(data()));
return message{std::move(ptr)};
return message{data_};
}
message message_builder::move_to_message() {
message result;
using pointer = detail::dynamic_message_data*;
result.vals().reset(static_cast<pointer>(data_.detach()), false);
return result;
return message{std::move(data_)};
}
message message_builder::extract(message_handler f) const {
......@@ -77,29 +74,13 @@ message message_builder::extract(message_handler f) const {
}
optional<message> message_builder::apply(message_handler handler) {
// avoid detaching of data_ by moving the data to a message object,
// calling message::apply and moving the data back
message::data_ptr ptr;
ptr.reset(static_cast<detail::dynamic_message_data*>(data_.detach()), false);
message msg{std::move(ptr)};
// Avoid detaching of data_ by moving the data to a message object,
// calling message::apply and moving the data back.
auto msg = move_to_message();
auto res = msg.apply(std::move(handler));
data_.reset(msg.vals().release(), false);
data_.reset(static_cast<detail::dynamic_message_data*>(msg.vals().release()),
false);
return res;
}
detail::dynamic_message_data* message_builder::data() {
// detach if needed, i.e., assume further non-const
// operations on data_ can cause race conditions if
// someone else holds a reference to data_
if (!data_->unique()) {
auto tmp = static_cast<detail::dynamic_message_data*>(data_.get())->copy();
data_.reset(tmp.release(), false);
}
return static_cast<detail::dynamic_message_data*>(data_.get());
}
const detail::dynamic_message_data* message_builder::data() const {
return static_cast<const detail::dynamic_message_data*>(data_.get());
}
} // namespace caf
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment