Skip to content

Copy on Write #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 153 additions & 20 deletions test/demo/type_erasure_shared_pointer_value_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,105 @@
#include "zoo/FunctionPolicy.h"

#include <catch2/catch.hpp>
#include <sstream>

// Primary template: defaults to false
template <typename, typename = void>
struct ExclusiveAwareTrait: std::false_type {};

// Specialization: if T has T::ExclusiveAware, this will match and be true
template <typename T>
struct ExclusiveAwareTrait<T, std::void_t<typename T::ExclusiveAware>>: std::true_type {};


struct MyCoolStruct {
struct ExclusiveAware {};
};

static_assert(ExclusiveAwareTrait<int>::value == false);
static_assert(ExclusiveAwareTrait<MyCoolStruct>::value);


struct StringInputOutput {
struct VTableEntry {
std::string (*str)(const void *);
void (*fromString)(void *, const std::string &);
};

template<typename>
constexpr static inline VTableEntry Default = {
[](const void *) { return std::string(); },
[](void *, const std::string &) {}
};

template<typename ConcreteValueManager>
constexpr static inline VTableEntry Operation = {
[](const void *container) {
auto c = const_cast<void *>(container);
auto cvm = static_cast<ConcreteValueManager *>(c);
std::ostringstream oss;
oss << *cvm->value();
return oss.str();
},
[] (void *container, const std::string &str) {
auto c = const_cast<void *>(container);
auto cvm = static_cast<ConcreteValueManager *>(c);

if constexpr (ExclusiveAwareTrait<ConcreteValueManager>::value) {
if (!cvm->isExclusive()) {
cvm->makeExclusive();
}
}

std::istringstream iss{str};
iss >> *cvm->value();
},
};

// No extra state/functions needed in the memory layout
template<typename Container>
struct Mixin {};

template<typename AnyC>
struct UserAffordance {

std::string stringize() const {
auto container =
const_cast<AnyC *>(static_cast<const AnyC *>(this))->container();
return container->template vTable<StringInputOutput>()->str(container);
}

auto fromString(const std::string &str) {
auto container = static_cast<AnyC *>(this)->container();
auto vt = container->template vTable<StringInputOutput>();
vt->fromString(container, str);
}
};
};


namespace user {

template<typename V, typename Policy>
auto extractSharedPointer(zoo::AnyContainer<Policy> &a) {
using VBuilder = typename Policy::template Builder<V>;
auto downcasted = static_cast<VBuilder *>(a.container());
return downcasted->sharedPointer();
auto valueManager = static_cast<VBuilder *>(a.container());
return valueManager->sharedPointer();
}

template<typename V, typename Policy>
auto isExclusive(zoo::AnyContainer<Policy> &a) {
if constexpr (SharedPointerOptIn<V>::value) {
using VBuilder = typename Policy::template Builder<V>;
auto valueManager = static_cast<VBuilder *>(a.container());
return valueManager->isExclusive();
}

return true;
}



}

using LocalBuffer = void *[4];
Expand All @@ -21,26 +110,70 @@ static_assert(sizeof(std::shared_ptr<int>) <= sizeof(LocalBuffer));
using UAny = zoo::AnyContainer<
user::SharedPointerPolicy<
LocalBuffer,
zoo::Destroy, zoo::Move, zoo::Copy, zoo::RTTI
zoo::Destroy, zoo::Move, zoo::RTTI
>
>;


TEST_CASE("Shared Pointer Value Manager", "[demo][type-erasure][shared-pointer-policy]") {
UAny uAny{9.9};
CHECK(9.9 == *uAny.state<double>());
user::ExplicitDestructor ed;
REQUIRE(nullptr == user::ExplicitDestructor::last);
uAny = ed;
CHECK(nullptr == user::ExplicitDestructor::last);
REQUIRE(typeid(user::ExplicitDestructor) == uAny.type());
auto spp = user::extractSharedPointer<user::ExplicitDestructor>(uAny);
auto sp = *spp;
REQUIRE(2 == sp.use_count());
CHECK(nullptr == user::ExplicitDestructor::last);
const auto oldAddress = uAny.state<user::ExplicitDestructor>();
REQUIRE(oldAddress == &*sp);
sp.reset();
REQUIRE(1 == spp->use_count());
uAny = 5;
REQUIRE(oldAddress == user::ExplicitDestructor::last);
SECTION("shared pointer value management basics") {
UAny uAny{9.9};
CHECK(9.9 == *uAny.state<double>());

REQUIRE(user::isExclusive<double>(uAny));

user::ExplicitDestructor ed;
REQUIRE(nullptr == user::ExplicitDestructor::last);
uAny = ed;
CHECK(nullptr == user::ExplicitDestructor::last);
REQUIRE(typeid(user::ExplicitDestructor) == uAny.type());
auto spp = user::extractSharedPointer<user::ExplicitDestructor>(uAny);
auto sp = *spp;

REQUIRE(2 == sp.use_count());

REQUIRE(! user::isExclusive<user::ExplicitDestructor>(uAny));

CHECK(nullptr == user::ExplicitDestructor::last);
const auto oldAddress = uAny.state<user::ExplicitDestructor>();
REQUIRE(oldAddress == &*sp);
sp.reset();
REQUIRE(user::isExclusive<user::ExplicitDestructor>(uAny));

uAny = 5;
REQUIRE(oldAddress == user::ExplicitDestructor::last);
}

SECTION("roundtrip io affordance") {
using IOType = zoo::AnyContainer<zoo::Policy<LocalBuffer, zoo::Destroy, zoo::Move, StringInputOutput>>;
IOType io = 8;
REQUIRE("8" == io.stringize());
io.fromString("42");
REQUIRE(42 == *io.state<int>());
}

SECTION("copy on write") {
using CopyOnWritable = zoo::AnyContainer<user::SharedPointerPolicy<LocalBuffer, zoo::Destroy, zoo::Move, zoo::Copy, StringInputOutput>>;

CopyOnWritable a = std::string{"foo"};
auto aState = a.state<std::string>();
REQUIRE("foo" == *aState);

REQUIRE(user::isExclusive<std::string>(a));

auto b = a;
REQUIRE(!user::isExclusive<std::string>(a));
// proves that a and b share the same object
REQUIRE(aState == b.state<std::string>());
// now we're going to write to b! then the copy must happen.
b.fromString("bar");

REQUIRE(user::isExclusive<std::string>(a));
REQUIRE(user::isExclusive<std::string>(b));

auto bState = b.state<std::string>();
REQUIRE(aState != bState);
REQUIRE("foo" == *aState);
REQUIRE("bar" == *bState);
}
}
44 changes: 36 additions & 8 deletions test/inc/demo/type_erasure_shared_pointer_value_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ struct ExplicitDestructor {

template<typename>
struct SharedPointerOptIn: std::false_type {};

template<>
struct SharedPointerOptIn<int>: std::true_type {};

template<>
struct SharedPointerOptIn<ExplicitDestructor>: std::true_type {};

template<>
struct SharedPointerOptIn<std::string>: std::true_type {};


template<typename HoldingModel, typename... AffordanceSpecifications>
struct UserValueManagement {
/// abbreviation
Expand All @@ -37,9 +43,16 @@ struct UserValueManagement {
using VP = std::shared_ptr<V>;
/// Abbreviation
using SPM = SharedPointerManager;


struct ExclusiveAware {};

SharedPointerManager(): Base(&Operations) {
new(sharedPointer()) VP;
}

// not part of the end-user interface
VP *sharedPointer() noexcept { return this->space_.template as<VP>(); }
// part of the end-user interface
V *value() noexcept { return &**sharedPointer(); }

const V *value() const noexcept {
Expand All @@ -57,27 +70,41 @@ struct UserValueManagement {
}

static void copyOp(void *to, const void *from) {
auto downcast = static_cast<const SPM *>(from);
new(to) SPM(*downcast);
auto downcast = static_cast<SPM *>(const_cast<void *>(from));
auto destValueManager = new(to) SPM;
*destValueManager->sharedPointer() = *downcast->sharedPointer();
}

constexpr static inline typename GP::VTable Operations = {
AffordanceSpecifications::template Operation<SPM>...
};

auto isExclusive() const noexcept {
auto sp = const_cast<SPM *>(this)->sharedPointer();
return 1 == sp->use_count();
}

auto makeExclusive() {
*sharedPointer() = std::make_shared<V>(*value());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that this would be an issue for the vast majority of cases, but maybe const qualify the lvalue reference so it always selects the copy constructor.

Suggested change
*sharedPointer() = std::make_shared<V>(*value());
*sharedPointer() = std::make_shared<V>(static_cast<const V &>(*value()));

}

// not user interface
SharedPointerManager(SharedPointerManager &&donor) noexcept:
Base(&Operations)
{
new(sharedPointer()) VP(std::move(*donor.sharedPointer()));
}

SharedPointerManager(const SharedPointerManager &donor) noexcept:
Base(&Operations)
{
new(sharedPointer()) VP(*const_cast<SPM &>(donor).sharedPointer());
}
// not user interface
// SharedPointerManager(const SharedPointerManager &model) noexcept:
// Base(&Operations)
// {
// new(sharedPointer()) VP(*const_cast<SPM &>(model).sharedPointer());
// }



// internal interface of Builder (important)
template<typename... Args>
SharedPointerManager(Args &&...args):
Base(&Operations)
Expand All @@ -91,6 +118,7 @@ struct UserValueManagement {
};

struct AdaptedPolicy: GP::Policy {
// Builders is the old name to refer to what I now call "Value Manager"
template<typename V>
using Builder =
std::conditional_t<
Expand Down